Analysis of Lambda Implementation Principle
In order to support functional programming, Java 8 introduces Lambda expressions, so how to implement Lambda expressions in Java 8? What will Lambda expressions generate after compiling?
One guess
Before going into further analysis, let's think about that every Lambda expression in Java 8 must have a functional interface corresponding to it. The difference between a functional interface and a normal interface can be referred to above. Then you may wonder whether Lambda expression can be transformed into an implementation class of its corresponding functional interface, and then call subclasses in a polymorphic way. How to achieve it?
Sample of 1 Lambda expression
@FunctionalInterface interface Print<T> { public void print(T x); } public class Lambda { public static void PrintString(String s, Print<String> print) { print.print(s); } public static void main(String[] args) { PrintString("test", (x) -> System.out.println(x)); } }
After compiler processing, the final generated code should be as follows:
2.1 guess 1
@FunctionalInterface interface Print<T> { public void print(T x); } class Lambda$$0 implements Print<String> { @Override public void print(String x) { System.out.println(x); } } public class Lambda { public static void PrintString(String s, Print<String> print) { print.print(s); } public static void main(String[] args) { PrintString("test", new Lambda$$0()); } }
2.2 guess 2
@FunctionalInterface interface Print<T> { public void print(T x); } public class Lambda { final class Lambda$$0 implements Print<String> { @Override public void print(String x) { System.out.println(x); } } public static void PrintString(String s, Print<String> print) { print.print(s); } public static void main(String[] args) { PrintString("test", new Lambda().new Lambda$$0()); } }
2.3 guess 3
@FunctionalInterface interface Print<T> { public void print(T x); } public class Lambda { public static void PrintString(String s, Print<String> print) { print.print(s); } public static void main(String[] args) { PrintString("test", new Print<String>() { @Override public void print(String x) { System.out.println(x); } }); } }
The above code, apart from a little bit longer in code length, runs the same way as the code implemented with Lambda expressions. So how does Java 8 actually work? Is it one of the three above implementations?
Two validation
1. Obtaining bytecode files by decompiling tools
In order to explore how Lambda expressions are implemented, it is necessary to study the bytecode file that Lambda table transitions eventually transform into. This requires a bytecode viewing tool and decompilation tool under the bin directory of jdk.
javap -p Lambda.class
The - p in the above command represents the output of all classes and members. After running the above command, the results are as follows:
Compiled from "Lambda.java" public class Lambda { public Lambda(); public static void PrintString(java.lang.String, Print<java.lang.String>); public static void main(java.lang.String[]); private static void lambda$0(java.lang.String); }
As can be seen from the above code, the compiler generates a private static function based on the Lambda expression. Note that what is said here is generation, not equivalence.
private static void lambda$0(java.lang.String);
2 Verify the correctness of the above transformation
@FunctionalInterface interface Print<T> { public void print(T x); } public class Lambda { public static void PrintString(String s, Print<String> print) { print.print(s); } private static void lambda$0(String s) { } public static void main(String[] args) { PrintString("test", (x) -> System.out.println(x)); } }
The above code does not report errors at compile time, but at run time, because there are two lambda lambda $0 functions, as shown below, which are run time errors.
Exception in thread "main" java.lang.ClassFormatError: Duplicate method name&signature in class file Lambda at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:760) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) at java.net.URLClassLoader.access$100(URLClassLoader.java:73) at java.net.URLClassLoader$1.run(URLClassLoader.java:368) at java.net.URLClassLoader$1.run(URLClassLoader.java:362) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:361) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
The above error code is decompiled by javap, and the members of the class output after decompilation are as follows
Compiled from "Lambda.java" public class Lambda { public Lambda(); public static void PrintString(java.lang.String, Print<java.lang.String>); private static void lambda$0(java.lang.String); public static void main(java.lang.String[]); private static void lambda$0(java.lang.String); }
You'll find that lambda lambda $0 appears twice, so when the code runs, you don't know which to call, so you'll throw an error.
With the above, you can know that Lambda expression in Java 8 will first generate a private static function, this private static function is the content of the Lambda expression, so the above code can initially be converted into the code shown below.
@FunctionalInterface interface Print<T> { public void print(T x); } public class Lambda { public static void PrintString(String s, Print<String> print) { print.print(s); } private static void lambda$0(String x) { System.out.println(x); } public static void main(String[] args) { PrintString("test", /**lambda expression**/); } }
How is the static lambda lambda $0 function called?
You can break points in the following ways, and you can see where there is a lambda expression, which runs into the function.
public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType) throws LambdaConversionException { AbstractValidatingLambdaMetafactory mf; mf = new InnerClassLambdaMetafactory(caller, invokedType, invokedName, samMethodType, implMethod, instantiatedMethodType, false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY); mf.validateMetafactoryArgs(); return mf.buildCallSite(); }
In this function, you can find that an internal class is generated for Lambda expression. To verify whether an internal class is generated, you can add - Djdk.internal.lambda.dumpProxyClasses at runtime. With this parameter, the generated internal class code is output to a file at runtime.
final class Lambda$$Lambda$1 implements Print { private Lambda$$Lambda$1(); public void print(java.lang.Object); }
If you run javap-c-p, the results are as follows
final class Lambda$$Lambda$1 implements Print { private Lambda$$Lambda$1(); Code: 0: aload_0 1: invokespecial #10 // Method java/lang/Object."<init>":()V 4: return public void print(java.lang.Object); Code: 0: aload_1 1: checkcast #14 // class java/lang/String 4: invokestatic #20 // Method Lambda.lambda$0:(Ljava/lang/String;)V 7: return }
From the bytecode instructions above, you can see that the private static method called is Lambda.lambda Lambda. lambda $0..
So the final Lambda expression is equivalent to the following
@FunctionalInterface interface Print<T> { public void print(T x); } public class Lambda { public static void PrintString(String s, Print<String> print) { print.print(s); } private static void lambda$0(String x) { System.out.println(x); } final class $Lambda$1 implements Print{ @Override public void print(Object x) { lambda$0((String)x); } } public static void main(String[] args) { PrintString("test", new Lambda().new $Lambda$1()); } }