What Lambda Expressions are compiled to? What is their runtime behavior? [Updated: Dec 3, 2016, Created: Dec 10, 2015] |
|
||
In prior versions of Java, we have been inlining custom code or custom functionality by wrapping it inside an anonymous class. Java 8 Lambda expression is a way to inline that functionality without creating anonymous class. Now we are able to pass plain method/functionality as arguments. In last topic, we demonstrated how to write Lambda expressions. Here we are going discuss what they are compiled to and how they get executed during runtime. No Anonymous Class generation during compile timeLambda Expressions are not completely Syntactic Sugar, meaning compiler doesn't translate them into something which is already understood by JVM. An example of syntactic sugar in Java is Similarly for lambda, a straight forward approach could be: just desugar the new syntax into anonymous classes during compile time. But conceptually that wouldn't make them true functions. Also anonymous classes have their own problems from day one, JVM has to perform rather expensive process of initiation a new class instance whenever it encounters an inline anonymous class. Unless the developer does some performance consideration while writing the code and initialize anonymous classes at instance level of enclosing class. But there is a lot of pain we have to go through to make our code efficient at the places where we could have just inline some functional piece of code. These are the reasons lambda designers didn't go for anonymous class approach. That also means by using lambda now, we can get rid of those anonymous class explosion in our output directory. For example you must have noticed that creating an anonymous class inside Foo.java results in something like Foo$1.class. So what they are compiled to?Lambda syntax written by the developer is desugared into JVM level instructions generated during compilation, meaning the actual responsibility of constructing lambda is deferred to runtime . Compile Time Instructions and Dynamic constructionTo make the Java less compile time strict, and to allow JVM languages to bind code at run-time, Java 7 introduced a new invokedynamic bytecode instruction to the JVM and a new What happens during compile time?Instead of generating direct bytecode for lambda (like proposed anonymous class syntactic sugar approach), compiler declares a recipe (via invokeDynamic instructions) and delegates the real construction approach to runtime. During compilation process following information is generated
Here's a very simple class involving lambda, we are going to see what it's going to compile into: public class Test { public static void main (String[] args) { Runnable r = () -> System.out.println(); } } Now let's use javap on above class, notice the desugared method lambda$main$0: D:\examples\lambdas>javac Test.java D:\examples\lambdas>javap -p Test Compiled from "Test.java" public class Test { public Test(); public static void main(java.lang.String[]); private static void lambda$main$0(); } Let's see verbose output, we are omitting some stuff here. We don't have to understand everything here though D:\examples\lambda>javap -verbose Test Classfile /D:/projects/logicbig/src/main/java/Test.class ..... Compiled from "Test.java" public class Test ..... Constant pool: #1 = Methodref #6.#16 // java/lang/Object."<init>":()V #2 = InvokeDynamic #0:#21 // #0:run:()Ljava/lang/Runnable; ...... #17 = Utf8 BootstrapMethods #18 = MethodHandle #6:#28 // 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; .......... #20 = MethodHandle #6:#29 // invokestatic Test.lambda$main$0:()V #21 = NameAndType #30:#31 // run:()Ljava/lang/Runnable; ......... #28 = Methodref #37.#38 // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invokeMethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; { public Test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 2: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=2, args_size=1 0: invokedynamic #2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable; 5: astore_1 6: return LineNumberTable: line 4: 0 line 5: 6 } SourceFile: "Test.java" InnerClasses: public static final #43= #42 of #46; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles BootstrapMethods: 0: #18 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; Method arguments: #19 ()V #20 invokestatic Test.lambda$main$0:()V #19 ()V D:\>java -version java version "1.8.0_65" Java(TM) SE Runtime Environment (build 1.8.0_65-b17) Java HotSpot(TM) 64-Bit Server VM (build 25.65-b01, mixed mode) What happens during runtime?During runtime, when JVM encounters JVM chooses a strategy to construct lambda. That strategy may involve anonymous inner class generation or MethodHandle (another dynamic language feature added in Java 7, similar to C function pointer that points to an executable code) or dynamic proxies or whatever is good in performance. invokedynamic turns that choice into pure JVM implementation details, hence separating that decision from compile time bytecode. What is Lambda Runtime Type to a developer?Let's consider the following example public class LambdaTest { public void aTestMethod() { Runnable runnable = () -> { System.out.println("this " + this); throw new RuntimeException(); }; System.out.println("class: " + runnable.getClass()); runnable.run(); } public static void main(String[] args) { LambdaTest lambdaTest = new LambdaTest(); lambdaTest.aTestMethod(); } } Here we are doing three different things together. Before running lambda run() method we printed the class type of lambda to console, then inside lambda body printed Let's compile, see some disassembled info (using javap) and run it: D:\examples\lambda>javac LambdaTest.java D:\examples\lambda>javap -p LambdaTest Compiled from "LambdaTest.java" public class LambdaTest { public LambdaTest(); public void aTestMethod(); public static void main(java.lang.String[]); private void lambda$aTestMethod$0(); } D:\examples\lambda>java LambdaTest class: class LambdaTest$$Lambda$1/791452441 this LambdaTest@1c20c684 Exception in thread "main" java.lang.RuntimeException at LambdaTest.lambda$aTestMethod$0(LambdaTest.java:6) at LambdaTest.aTestMethod(LambdaTest.java:9) at LambdaTest.main(LambdaTest.java:14)
Let's use some reflection to find more information about import java.lang.reflect.Method; public class LambdaTest { public void aTestMethod() { Runnable runnable = () -> { System.out.println("this " + this); //throw new RuntimeException(); }; System.out.println("class: " + runnable.getClass()); runnable.run(); } public static void main(String[] args) { // LambdaTest lambdaTest = new LambdaTest(); // lambdaTest.aTestMethod(); for (Method method : LambdaTest.class.getDeclaredMethods()) { System.out.println(method); } } } Here's the output: D:\examples\lambda>java LambdaTest public static void LambdaTest.main(java.lang.String[]) public void LambdaTest.aTestMethod() private void LambdaTest.lambda$aTestMethod$0() That confirms that |
|
||
|