概述
Java8引入了lambda表达式,那么底层是如何实现的呢?是否是采用匿名内部类实现的呢?
代码样例
public class MyTest {
public static void main(String[] args) {
Runnable r = () -> System.out.println(Arrays.toString(args));
r.run();
}
}
字节码如下(javap -c -s -l -verbose -private lamb/MyTest.class):
Classfile /Users/xmly/Works/example/target/classes/lamb/MyTest.class
Last modified 2022-10-24; size 1270 bytes
MD5 checksum 308b472813d9700f4dca0763aeeb505a
Compiled from "MyTest.java"
public class lamb.MyTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#25 // java/lang/Object."":()V
#2 = InvokeDynamic #0:#30 // #0:run:([Ljava/lang/String;)Ljava/lang/Runnable;
#3 = InterfaceMethodref #31.#32 // java/lang/Runnable.run:()V
#4 = Fieldref #33.#34 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #35.#36 // java/util/Arrays.toString:([Ljava/lang/Object;)Ljava/lang/String;
#6 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V
#7 = Class #39 // lamb/MyTest
#8 = Class #40 // java/lang/Object
#9 = Utf8
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Llamb/MyTest;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 args
#19 = Utf8 [Ljava/lang/String;
#20 = Utf8 r
#21 = Utf8 Ljava/lang/Runnable;
#22 = Utf8 lambda$main$0
#23 = Utf8 SourceFile
#24 = Utf8 MyTest.java
#25 = NameAndType #9:#10 // "":()V
#26 = Utf8 BootstrapMethods
#27 = MethodHandle #6:#41 // 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;
#28 = MethodType #10 // ()V
#29 = MethodHandle #6:#42 // invokestatic lamb/MyTest.lambda$main$0:([Ljava/lang/String;)V
#30 = NameAndType #43:#44 // run:([Ljava/lang/String;)Ljava/lang/Runnable;
#31 = Class #45 // java/lang/Runnable
#32 = NameAndType #43:#10 // run:()V
#33 = Class #46 // java/lang/System
#34 = NameAndType #47:#48 // out:Ljava/io/PrintStream;
#35 = Class #49 // java/util/Arrays
#36 = NameAndType #50:#51 // toString:([Ljava/lang/Object;)Ljava/lang/String;
#37 = Class #52 // java/io/PrintStream
#38 = NameAndType #53:#54 // println:(Ljava/lang/String;)V
#39 = Utf8 lamb/MyTest
#40 = Utf8 java/lang/Object
#41 = Methodref #55.#56 // 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;
#42 = Methodref #7.#57 // lamb/MyTest.lambda$main$0:([Ljava/lang/String;)V
#43 = Utf8 run
#44 = Utf8 ([Ljava/lang/String;)Ljava/lang/Runnable;
#45 = Utf8 java/lang/Runnable
#46 = Utf8 java/lang/System
#47 = Utf8 out
#48 = Utf8 Ljava/io/PrintStream;
#49 = Utf8 java/util/Arrays
#50 = Utf8 toString
#51 = Utf8 ([Ljava/lang/Object;)Ljava/lang/String;
#52 = Utf8 java/io/PrintStream
#53 = Utf8 println
#54 = Utf8 (Ljava/lang/String;)V
#55 = Class #58 // java/lang/invoke/LambdaMetafactory
#56 = NameAndType #59:#63 // 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;
#57 = NameAndType #22:#17 // lambda$main$0:([Ljava/lang/String;)V
#58 = Utf8 java/lang/invoke/LambdaMetafactory
#59 = Utf8 metafactory
#60 = Class #65 // java/lang/invoke/MethodHandles$Lookup
#61 = Utf8 Lookup
#62 = Utf8 InnerClasses
#63 = Utf8 (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;
#64 = Class #66 // java/lang/invoke/MethodHandles
#65 = Utf8 java/lang/invoke/MethodHandles$Lookup
#66 = Utf8 java/lang/invoke/MethodHandles
{
public lamb.MyTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Llamb/MyTest;
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: aload_0
1: invokedynamic #2, 0 // InvokeDynamic #0:run:([Ljava/lang/String;)Ljava/lang/Runnable;
6: astore_1
7: aload_1
8: invokeinterface #3, 1 // InterfaceMethod java/lang/Runnable.run:()V
13: return
LineNumberTable:
line 7: 0
line 8: 7
line 9: 13
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 args [Ljava/lang/String;
7 7 1 r Ljava/lang/Runnable;
private static void lambda$main$0(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokestatic #5 // Method java/util/Arrays.toString:([Ljava/lang/Object;)Ljava/lang/String;
7: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String;
}
SourceFile: "MyTest.java"
InnerClasses:
public static final #61= #60 of #64; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #27 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:
#28 ()V
#29 invokestatic lamb/MyTest.lambda$main$0:([Ljava/lang/String;)V
#28 ()V
分析
可以看到编译后的字节码中出现了几个新的内容,lambda表达式编译后变成了
invokedynamic #2, 0 //0 是预留参数,暂时没有作用
#2 = InvokeDynamic #0:#30
#30 = NameAndType #43:#44
#43 = Utf8 run
#44 = Utf8 ([Ljava/lang/String;)Ljava/lang/Runnable;
其中0为表示在 [Bootstrap methods表]中的索引:
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;
javac编译
可以看到编译后的字节码里面多了BSM相关的信息,说明javac编译对lambda做了特殊处理:
javac命令的代码调用路径为:
com.sun.tools.javac.Main->com.sun.tools.javac.main.Main->com.sun.tools.javac.main.JavaCompiler.compile2()->com.sun.tools.javac.main.JavaCompiler.desugar()
if (this.source.allowLambda() && scanner.hasLambdas) {
if (this.shouldStop(CompileState.UNLAMBDA)) {
return;
}
env.tree = LambdaToMethod.instance(this.context).translateTopLevelClass(env, env.tree, localMake);
this.compileStates.put(env, CompileState.UNLAMBDA);
}
处理BSM相关的代码如下:
private JCExpression makeMetafactoryIndyCall(LambdaToMethod.LambdaAnalyzerPreprocessor.TranslationContext> context, int refKind, Symbol refSym, List indy_args) {
JCFunctionalExpression tree = context.tree;
MethodSymbol samSym = (MethodSymbol)this.types.findDescriptorSymbol(tree.type.tsym);
List
从Name metafactoryName = context.needsAltMetafactory() ? this.names.altMetafactory : this.names.metafactory;
可以知道lambda的BSM有两种:
- java.lang.invoke.LambdaMetafactory.metafactory
- java.lang.invoke.LambdaMetafactory.altMetafactory
调用链观察
从上面的分析,可以知道lambda最终会调用LambdaMetafactory.metafactory,通过打断点观察,其传入参数如下:
LambdaMetafactory.metafactory最终是调用InnerClassLambdaMetafactory.buildCallSite,代码如下:
final Class> innerClass = spinInnerClass();
if (invokedType.parameterCount() == 0) {
final Constructor>[] ctrs = AccessController.doPrivileged(
new PrivilegedAction[]>() {
@Override
public Constructor>[] run() {
Constructor>[] ctrs = innerClass.getDeclaredConstructors();
if (ctrs.length == 1) {
// The lambda implementing inner class constructor is private, set
// it accessible (by us) before creating the constant sole instance
ctrs[0].setAccessible(true);
}
return ctrs;
}
});
if (ctrs.length != 1) {
throw new LambdaConversionException("Expected one lambda constructor for "
+ innerClass.getCanonicalName() + ", got " + ctrs.length);
}
try {
Object inst = ctrs[0].newInstance();
return new ConstantCallSite(MethodHandles.constant(samBase, inst));
}
catch (ReflectiveOperationException e) {
throw new LambdaConversionException("Exception instantiating lambda object", e);
}
} else {
try {
UNSAFE.ensureClassInitialized(innerClass);
return new ConstantCallSite(
MethodHandles.Lookup.IMPL_LOOKUP
.findStatic(innerClass, NAME_FACTORY, invokedType));
}
catch (ReflectiveOperationException e) {
throw new LambdaConversionException("Exception finding constructor", e);
}
}
spinInnerClass方法会使用unsafe.defineAnonymousClass来生成VM匿名内部类:
package lamb;
import java.lang.invoke.LambdaForm.Hidden;
// $FF: synthetic class
final class MyTest$$Lambda$1 implements Runnable {
private final String[] arg$1;
private MyTest$$Lambda$1(String[] var1) {
this.arg$1 = var1;
}
private static Runnable get$Lambda(String[] var0) {
return new MyTest$$Lambda$1(var0);
}
@Hidden
public void run() {
MyTest.lambda$main$0(this.arg$1);
}
}
可以看到实际上是一个实现了Runnable接口的匿名类,最终会调用MyTest.lambda0方法;
打印匿名类内容需要增加配置:-Djdk.internal.lambda.dumpProxyClasses=
VM anonymous class与匿名内部类
具体介绍可以参考这篇文章:https://www.zhihu.com/question/51132462/answer/124751186