How Lambdas Are Compiled
A lambda looks like a short form of an anonymous class, but the compiler treats it very differently. An anonymous class becomes a separate .class file. A lambda becomes a private method in the same class, plus a single invokedynamic instruction that asks the JVM, at runtime, to create an object implementing the functional interface. The JVM does this with a helper called LambdaMetafactory. In this lesson, you will look inside the compiled code to see exactly what happens, and learn why this design makes lambdas efficient.
Example: One Lambda and One Anonymous Class
Let's compile a class that contains one of each and see what files appear.
import java.util.function.Function;
public class Hello {
public static void main(String[] args) {
Function<String, String> greet = name -> "Hi " + name;
System.out.println(greet.apply("Navika"));
Runnable r = new Runnable() {
public void run() { }
};
}
}Hi Navikajavac Hello.java
ls *.classHello$1.class
Hello.classExplanation: The anonymous Runnable became its own file, Hello$1.class. The lambda produced no extra class file at all.
Step 1: The Lambda Body Becomes a Private Method
The javap tool shows the compiled bytecode. Running javap -c -p Hello.class prints:
Compiled from "Hello.java"
public class Hello {
public Hello();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokedynamic #7, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function;
5: astore_1
6: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
9: aload_1
10: ldc #17 // String Navika
12: invokeinterface #19, 2 // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
17: checkcast #24 // class java/lang/String
20: invokevirtual #26 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: new #32 // class Hello$1
26: dup
27: invokespecial #34 // Method Hello$1."<init>":()V
30: astore_2
31: return
private static java.lang.String lambda$main$0(java.lang.String);
Code:
0: aload_0
1: invokedynamic #35, 0 // InvokeDynamic #1:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
6: areturn
}Explanation:
- The lambda's body,
"Hi " + name, was moved into a new private static method namedlambda$main$0. (It's static because the lambda doesn't usethis; a lambda that uses instance fields becomes an instance method.) - Where the lambda was written,
mainnow has aninvokedynamicinstruction (at 0). It returns aFunctionobject. - The anonymous class is created the old way, with
new Hello$1.
Step 2: invokedynamic and LambdaMetafactory
invokedynamic is a JVM instruction, added in Java 7, whose target is decided at runtime the first time it runs. Each invokedynamic points to a bootstrap method. For lambdas, that bootstrap method is LambdaMetafactory.metafactory. javap -v shows it in the class file's BootstrapMethods table, together with the method handle for lambda$main$0.
The first time the instruction runs:
- The JVM calls
LambdaMetafactory.metafactory, passing the functional interface (Function), its method name (apply) and a handle tolambda$main$0. LambdaMetafactorygenerates, in memory, a small hidden class that implementsFunctionand whoseapplycallslambda$main$0.- The result is linked to the call site, so later executions skip all of this and simply return the object. For a lambda that captures no variables, it's the same object every time.
You can ask the JVM to save those hidden classes to disk to see them:
java -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=true Hello
ls DUMP_LAMBDA_PROXY_CLASS_FILESHi Navika
Hello$$Lambda.0x00003fc001040210.classExplanation: The class name includes a number that changes from run to run, because the class is generated while the program runs, not by javac.
Why This Design Is Better
- Smaller programs: no class file per lambda, so JAR files are smaller and load faster.
- Lazy: the implementation class is created only if the lambda is actually used.
- Flexible: because the strategy lives in the JVM, not in your compiled code, future JVMs can generate lambdas in better ways without recompiling.
- Cheap reuse: non-capturing lambdas are created once and shared.
Common Mistakes
Saying "a lambda is just an anonymous class". It compiles to a private method and an invokedynamic, with no class file.
Thinking lambdas are slow because of the runtime setup. The setup happens once per call site; later calls are as fast as a normal interface call.
Interview Corner
How are lambda expressions compiled in Java?
The compiler moves the lambda's body into a private synthetic method, such as lambda$main$0, and replaces the lambda expression with an invokedynamic instruction whose bootstrap method is LambdaMetafactory.metafactory. On first execution, the JVM calls the bootstrap method, which generates a hidden class implementing the functional interface that delegates to the synthetic method, and links the call site. Later executions reuse the linked call site. Unlike anonymous classes, no separate class file is produced at compile time.
Practice
- Easy: Compile a class with three lambdas and run
javap -pon it. How manylambda$methods do you see? - Harder: Write one lambda that uses an instance field and one that uses nothing. Use
javap -pto check whichlambda$method is static and which isn't, and explain why.
Key Takeaways
Was this tutorial helpful?