You may wonder how the Java compiler implements lambda expressions and how the Java virtual machine (JVM) deals with them. If you think lambda expressions can simply be translated to anonymous classes, you should read on. This appendix briefly discusses how lambda expressions are compiled, by examining the generated class files.
We showed in chapter 2 that anonymous classes can be used to declare and instantiate a class at the same time. As a result, just like lambda expressions, they can be used to provide the implementation for a functional interface.
Because a lambda expression provides the implementation for the abstract method of a functional interface, it would seem straightforward to ask the Java compiler to translate a lambda expression into an anonymous class during the compilation process. But anonymous classes have some undesirable characteristics that impact the performance of applications:
A Java source file is compiled to Java bytecode by the Java compiler. The JVM can then execute the generated bytecode and run the application. Anonymous classes and lambda expressions use different bytecode instructions when they’re compiled. You can inspect the bytecode and constant pool of any class file using the command
javap -c -v ClassName
Let’s try to implement an instance of the Function interface using the old Java 7 syntax, as an anonymous inner class, as shown in the following listing.
import java.util.function.Function; public class InnerClass { Function<Object, String> f = new Function<Object, String>() { @Override public String apply(Object obj) { return obj.toString(); } }; }
Doing this, the corresponding generated bytecode for the Function created as an anonymous inner class will be something along the lines of this:
0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: new #2 // class InnerClass$1 8: dup 9: aload_0 10: invokespecial #3 // Method InnerClass$1."<init>":(LInnerClass;)V 13: putfield #4 // Field f:Ljava/util/function/Function; 16: return
This code shows the following:
InnerClass$1 is the name generated by the compiler for the anonymous class. If you want to reassure yourself, you can inspect the InnerClass$1 class file as well, and you’ll find the code for the implementation of the Function interface:
class InnerClass$1 implements java.util.function.Function<java.lang.Object, java.lang.String> { final InnerClass this$0; public java.lang.String apply(java.lang.Object); Code: 0: aload_1 1: invokevirtual #3 //Method java/lang/Object.toString:()Ljava/lang/String; 4: areturn }
Now let’s try to do the same using the new Java 8 syntax as a lambda expression. Inspect the generated class file of the code in the following listing.
import java.util.function.Function; public class Lambda { Function<Object, String> f = obj -> obj.toString(); }
You’ll find the following bytecode instructions:
0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: invokedynamic #2, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function; 10: putfield #3 // Field f:Ljava/util/function/Function; 13: return
We explained the drawbacks in translating a lambda expression in an anonymous inner class, and indeed you can see that the result is very different. The creation of an extra class has been replaced with an invokedynamic instruction.
The bytecode instruction invokedynamic was introduced in JDK7 to support dynamically typed languages on the JVM. invokedynamic adds a further level of indirection when invoking a method, to let some logic dependent on the specific dynamic language determine the call target. The typical use for this instruction is something like the following:
def add(a, b) { a + b }
Here the types of a and b aren’t known at compile time and can change from time to time. For this reason, when the JVM executes an invokedynamic for the first time, it consults a bootstrap method, implementing the language-dependent logic that determines the actual method to be called. The bootstrap method returns a linked call site. There’s a good chance that if the add method is called with two ints, the subsequent call will also be with two ints. As a result, it’s not necessary to rediscover the method to be called at each invocation. The call site itself can contain the logic defining under which conditions it needs to be relinked.
In listing D.2, the features of the invokedynamic instruction have been used for a slightly different purpose than the one for which they were originally introduced. In fact, here it’s used to delay the strategy used to translate lambda expressions in bytecode until runtime. In other words, using invokedynamic in this way allows deferring code generation for implementing the lambda expression until runtime. This design choice has positive consequences:
A lambda expression is translated into bytecode by putting its body into one of a static method created at runtime. A stateless lambda, one that captures no state from its enclosing scope, like the one we defined in listing D.2, is the simplest type of lambda to be translated. In this case the compiler can generate a method having the same signature of the lambda expression, so the result of this translation process can be logically seen as follows:
public class Lambda { Function<Object, String> f = [dynamic invocation of lambda$1] static String lambda$1(Object obj) { return obj.toString(); } }
The case of a lambda expression capturing final (or effectively final) local variables or fields, as in the following example, is a bit more complex:
public class Lambda { String header = "This is a "; Function<Object, String> f = obj -> header + obj.toString(); }
In this case the signature of the generated method can’t be the same as the lambda expression, because it’s necessary to add extra arguments to carry the additional state of the enclosed context. The simplest solution to achieve this is to prepend the arguments of the lambda expression with an additional argument for each of the captured variables, so the method generated to implement the former lambda expression will be something like this:
public class Lambda { String header = "This is a "; Function<Object, String> f = [dynamic invocation of lambda$1] static String lambda$1(String header, Object obj) { return obj -> header + obj.toString(); } }
More information about the translation process for lambda expressions can be found here: http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html.