Java 8 Lambda : An attempt to decode

Introduction

This article will look into Java 8 Lambda expressions and how they are currently implemented using JVM internals.

Pre Lambda world

Long before lambda expressions we used the language feature called anonymous inner class. According to oracle documentation anonymous inner classes are a concise way to provide implementation for a type and also instantiate it at the same time. Anonymous inner classes have been used largely in UI frameworks like JavaFx as event handlers to implement Callback pattern. A common usage is of this form:

 Button btn = new Button();
 btn.setText("Click on this");
 btn.setOnAction(new EventHandler<ActionEvent>() {
 
      @Override
      public void handle(ActionEvent event) {
                System.out.println("Hello World!");
      }
 });

But anonymous inner classes are lacking certain features:

  • They could lead to bulky and spaghetti code to an existing code block.
  • Inability to capture non-final local variables.
  • Creation of multiple instances every time the same block of code is executed.

One of the problems which anonymous inner classes are known for is that they cause memory leaks. A safe way to use anonymous inner classes is to use them as static inner class or don't use them at all.

Enters Lambda

According to JSR-335 the lamdba expressions were introduced to overcome some of the problems of anonymous inner classes. A detailed overview of Lambda expressions is provided at the following link on openjdk. Lambda are an improvement on inner classes in a sense that they are more concise and allow usage of effectively final variables.

One thing to note here is that lambda expressions are introduced to replace anonymous inner classes of special types. These special types are interfaces which have only one abstract method for example Comparator<T>. So you are still stuck with anonymous inner classes (though they are discouraged) for types with more than one method. An example of functional interface:

@FunctionalInterface
interface Animal {
    public void walk(Integer steps);
}

Now, adding an annotation to your functional interface is not mandatory but it is a best practice since compiler will complain if the targeted interface does not follow rules of a functional interface. These are:

  • should have exactly one abstract method.
  • can have one or more default methods since they are considered implementations.

If you look at the bytecode of the functional interface it is same as any regular interface except if you use the @FunctionalInterface annotation since this annotation is retained for runtime and will be part of bytecode.

Lamdba expressions v/s Anonymous inner classes

Let us use the functional interface Animal both as a lambda expression and anonymous inner class.

public class MyLambdaClass {


    public static void main(String... args) {
        walk(10, s -> {
            System.out.println("I am walking " + s + " steps");
        });
    }

    public static void walk(Integer steps, Animal animal) {
        animal.walk(steps);
    }

}
....

public class MyInnerClass {


    public static void main(String... args) {
        walk(10, new Animal() {
            @Override
            public void walk(Integer steps) {
                System.out.println("I am walkin " + steps + " Steps");
            }

        });
    }

    public static void walk(Integer steps, Animal animal) {
        animal.walk(steps);
    }


}

If we look at the bytecode of both the implementations we have some variations.

MyInnerClass.java

// class version 52.0 (52)
// access flags 0x21
public class com/inovia/publication/bookservice/service/MyInnerClass {

  // compiled from: MyInnerClass.java
  // access flags 0x8
  static INNERCLASS com/inovia/publication/bookservice/service/MyInnerClass$1 null null

.
.
.
// access flags 0x89
  public static varargs main([Ljava/lang/String;)V
   L0
    LINENUMBER 10 L0
    BIPUSH 10
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    NEW com/inovia/publication/bookservice/service/MyInnerClass$1
    DUP
    INVOKESPECIAL com/inovia/publication/bookservice/service/MyInnerClass$1.<init> ()V
    INVOKESTATIC com/inovia/publication/bookservice/service/MyInnerClass.walk (Ljava/lang/Integer;Lcom/inovia/publication/bookservice/service/Animal;)V
   L1
    LINENUMBER 17 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    MAXSTACK = 3
    MAXLOCALS = 1
 
  

MyLambdaClass.java

// class version 52.0 (52)
// access flags 0x21
public class com/inovia/publication/bookservice/service/MyLambdaClass {

  // compiled from: MyLambdaClass.java
  // access flags 0x19
  public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
.
.
.
.
// access flags 0x89
  public static varargs main([Ljava/lang/String;)V
   L0
    LINENUMBER 10 L0
    BIPUSH 10
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    INVOKEDYNAMIC walk()Lcom/inovia/publication/bookservice/service/Animal; [
      // handle kind 0x6 : INVOKESTATIC
      java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
      // arguments:
      (Ljava/lang/Integer;)V, 
      // handle kind 0x6 : INVOKESTATIC
      com/inovia/publication/bookservice/service/MyLambdaClass.lambda$main$0(Ljava/lang/Integer;)V, 
      (Ljava/lang/Integer;)V
    ]
    INVOKESTATIC com/inovia/publication/bookservice/service/MyLambdaClass.walk (Ljava/lang/Integer;Lcom/inovia/publication/bookservice/service/Animal;)V
   L1
    LINENUMBER 13 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1
.
.
.
.
.
.
.

// access flags 0x100A
  private static synthetic lambda$main$0(Ljava/lang/Integer;)V
   L0
    LINENUMBER 11 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "I am walking "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder;
    LDC " steps"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 12 L1
    RETURN
   L2
    LOCALVARIABLE s Ljava/lang/Integer; L0 L2 0
    MAXSTACK = 3
    MAXLOCALS = 1
}

Notable differences between the two:

  • Anonymous Inner classes are compiled as an inner class i.e in this case MyInnerClass$1. An instance of this inner class is created each time new Animal(){ .. } is called.
  • Lambda expressions are implemented as static methods inside the main class. This can be seen with definition static synthetic lambda$main$0(Ljava/lang/Integer;).
  • This static method is called using invokedynamic instruction of bytecode. Invokedynamic construct was introduced in Java SE 7 to support dynamically typed languages like JavaScript and Ruby but its real purpose is to create a runtime link between the calling method and method implementation.
  • New Invoke api in Java language is equivalent of Invokedynamic instruction in JVM. In the bytecode there is reference to Ljava/lang/invoke/MethodHandles$Lookup and Ljava/lang/invoke/MethodType.

Java Invoke API

Java reflection api is bad in performance and is difficult to debug. The new java.lang.invoke api replaces the old reflection api. Read more about it here.

Conclusion

In this article I tried to explain how Lambda expressions are working in Java 8. A thing to note here is that JVM implementation of lambda using invokedynamic points to the fact that Java language designers do not want to fix the way lamdba are implemented in JVM.

Thus we should not base our usage of lambda expression on the resultant jvm bytecode as it might change in future JVM releases. However we should use lambda expressions where ever it is possible because it does not have some of the disadvantages that anonymous inner classes have.


References

  • https://docs.oracle.com/javase/8/docs/technotes/guides/vm/multiple-language-support.htm

To view or add a comment, sign in

More articles by Vivek Malhotra

Others also viewed

Explore content categories