基础概念
lambda表达式
lambda是java8加入的语法糖,它是一个匿名函数,即没有声明的方法,它无访问修饰符、无返回值声明和方法名称。语法格式如下:
(arg1, arg2...) -> { body}
- 当只有一个参数且不声明类型时,可以省略参数的括号。
- 当body部分只有一个语句时,可以省略大括号。
- 参数类型可以显示声明,也可以由编译器自动从上下文推倒。如
(String s) -> System.out.println(s)
函数式接口
在 Java 中,函数式接口(Functional interface)指只有一个抽象方法的接口。可以添加@FunctionalInterface
注解明确设计意图,如果接口中的方法多于一个时,编译时就会产生错误。
lambda表达式可以隐式的分配给功能接口。如Runnable接口的创建,可以写成如下形式:
Runnable r = () -> System.out.println("hello lambda");
又如:
new Thread(
() -> System.out.println("hello runnable");
)
java8 lambda的实现
在JDK,lambda表达式这里使用了invokedynamic
指令,它是java 7引入的新虚拟机指令,也是Java1.0以来第一次引入的新的指令,在java 8中开始正式使用。它与其他invoke指令(invokevirutal,invokestatic, invokeinterface,invokespecial)最大的区别在于它在运行时通过Bootstrap Method简称(BSM)机制动态确定方法所属者和类型。第一次运行时BSM会返回一个调用点(CallSite)对象,并和invokedynamic指令链接,后续调用不再生成CallSite对象和链接操作。
下面我们通过以下示例来看一下java8 中用invokedynamic实现lambda的具体原理。
测试demo
//Java8.java
class Java8 {
interface Logger {
void log(String s);
}
public static void main(String... args) {
myPrint(s->System.out.println(s));
}
private static void myPrint(Logger logger) {
logger.log("hello lambda");
}
}
执行:
javac Java8.java
javap -v -p Java8.class
输出如下:
Classfile /Users/wuzhentang/workspace/java/desugared/Java8.class
Last modified 2021-4-5; size 1097 bytes
MD5 checksum 0314827cc5a7d10c76a7d9907982c371
Compiled from "java8.java"
class Java8
...
Constant pool:
#1 = Methodref #9.#25 // java/lang/Object."":()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;
.....
#49 = Utf8 println
#50 = Class #53 // java/lang/invoke/LambdaMetafactory
#51 = NameAndType #54:#57 // 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;
#52 = NameAndType #21:#22 // lambda$main$0:(Ljava/lang/String;)V
#53 = Utf8 java/lang/invoke/LambdaMetafactory
#54 = Utf8 metafactory
#55 = Class #59 // java/lang/invoke/MethodHandles$Lookup
#56 = Utf8 Lookup
#57 = 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;
#58 = Class #60 // java/lang/invoke/MethodHandles
#59 = Utf8 java/lang/invoke/MethodHandles$Lookup
#60 = Utf8 java/lang/invoke/MethodHandles
{
Java8();
descriptor: ()V
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 2: 0
public static void main(java.lang.String...);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
Code:
stack=1, locals=1, args_size=1
0: invokedynamic #2, 0 // InvokeDynamic #0:log:()LJava8$Logger;调用lambda表达式变成了invokedynamic指令
5: invokestatic #3 // Method myPrint:(LJava8$Logger;)V
8: return
LineNumberTable:
line 8: 0
line 9: 8
private static void myPrint(Java8$Logger);
descriptor: (LJava8$Logger;)V
flags: ACC_PRIVATE, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: ldc #4 // String hello lambda
3: invokeinterface #5, 2 // InterfaceMethod Java8$Logger.log:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 12: 0
line 13: 8
//新增的方法
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 #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
7: return
LineNumberTable:
line 8: 0
}
SourceFile: "java8.java"
InnerClasses:
static #11= #10 of #8; //Logger=class Java8$Logger of class Java8
public static final #56= #55 of #58; //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 (Ljava/lang/String;)V
#29 invokestatic Java8.lambda$main$0:(Ljava/lang/String;)V
#28 (Ljava/lang/String;)V
可以看到main方法中的lambda表达式变成了invokedynamic
指令,并新增了一个新增的私有方法lambda$main$0
,lambda$main$0
函数的逻辑就是lambda表达式中逻辑。
invokedynamic
指令的第一个参数0表示的是在BootstrapMethods
区中索引的值。这里BootstrapMethods
索引0处格式化处后为:
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;
查看LambdaMetafactory
[源码],(https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java)该方法会在运行时动态在内存中生成一个实现函数式接口实例类型,并在其接口实现中调用在Java8中新增的lambda$main$0
方法。我们可以使用以下命令验证:
java -Djdk.internal.lambda.dumpProxyClasses Java8
我们会在运行目录下看到多了一个Java8$$Lambda$1.class
文件,用javap
看一下:
javap -p -c Java8\$\$Lambda\$1.class
可以看到:
final class Java8$$Lambda$1 implements Java8$Logger {
private Java8$$Lambda$1();
Code:
0: aload_0
1: invokespecial #10 // Method java/lang/Object."":()V
4: return
public void log(java.lang.String);
Code:
0: aload_1
1: invokestatic #18 // Method Java8.lambda$main$0:(Ljava/lang/String;)V
4: return
}
确实在运行时生成了一个Java8$$Lambda$1
类,并且接口实现为调用生成的Java8.lambda$main$0
方法。
到这里整个lambda实现原理就比较清晰了,大致流程为:
lambda -----> invokedynamic(.classs), lambda$main$0 ------> Java8$Logger.class(在内存) ---> Java8$Logger.log ---> lambda$main$0
^ ^
| |
javac LambdaMetafactory.metafactory
D8实现lambda脱糖的基本原理
原理
Android 官方文档 或 AOSP 的源码中是没有LambdaMetafactory
类的,因此就无法通过java.lang.invoke.LambdaMetafactory
类中的metafactory
方法,运行时中即时创建 lambda相关的匿名类,Android d8中的实现基本原理为:
为lambda表达式生成一个类,类名类似于:
$$Lambda$xxx$yyy
,其中xxx
为lambda源码实现所在的类名,yyy
为一串类似md5字符串的值。具体生成算法暂未深入研究。该类会继承于函数式接口,并且有一个public static
的名为INSTANCE
的字段,该字段的类型为$$Lambda$xxx$yyy
,在类cinit
中(经典的单例实现)。接口的抽象方法在实现很简单,就是调用一个名为xxx.lambda$zzz$N
方法,xxx
为lambda源码实现所在的类名,zzz
为lambda源码实现所在的类的方法名,N
为阿拉伯数字。xxx.lambda$zzz$N
其实就是lambda表达式的具体逻辑的藏身之处。xxx.lambda$zzz$N
做为lambda实现的类,它是一个位于lambda实现类中的静态方法。如前文所述,它就是lambda表达式的具体实现。在lambda表达式调用的地方,会将lambda表达式替换为
$$Lambda$xxx$yyy.INSTANCE
。在后续调用接口方法,就直接调用$$Lambda$xxx$yyy.INSTANCE
的接口实现,接着转调到实现类中的xxx.lambda$zzz$N
方法,执行lambda真正的逻辑。
实例
源码使用测试Demo相同的代码。
编译并dex:
javac *.java
java -jar $ANDROID_HOME/build-tools/28.0.3/lib/d8.jar \
--lib $ANDROID_HOME/platforms/android-28/android.jar \
--release \
--output . \
*.class
解压出dex:
$ANDROID_HOME/build-tools/28.0.3/dexdump -d classes.dex
输出:
Processing 'classes.dex'...
Opened 'classes.dex', DEX version '035'
// Logger接口
Class #0 -
Class descriptor : 'LJava8$Logger;'
Access flags : 0x0600 (INTERFACE ABSTRACT)
Superclass : 'Ljava/lang/Object;'
...
#0 : (in LJava8$Logger;)
name : 'log'
type : '(Ljava/lang/String;)V'
...
Class #1 -
Class descriptor : 'LJava8;'
Access flags : 0x0000 ()
Superclass : 'Ljava/lang/Object;'
Interfaces -
Static fields -
Instance fields -
Direct methods -
#0 : (in LJava8;)
name : ''
...
// 生成一个名为'lambda$main$0'的静态方法
#1 : (in LJava8;)
name : 'lambda$main$0'
type : '(Ljava/lang/String;)V'
access : 0x1008 (STATIC SYNTHETIC)
code -
registers : 2
ins : 1
outs : 2
insns size : 6 16-bit code units
// lambda$main$0方法的实现
// System.out.println(s)
000270: |[000270] Java8.lambda$main$0:(Ljava/lang/String;)V
000280: 6200 0100 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0001
000284: 6e20 0800 1000 |0002: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0008
....
//main方法
#2 : (in LJava8;)
name : 'main'
type : '([Ljava/lang/String;)V'
access : 0x0089 (PUBLIC STATIC VARARGS)
....
00028c: |[00028c] Java8.main:([Ljava/lang/String;)V
//获取$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY类中名为INSTANCE的字段(它是$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY类的单例),存到变量v0中
00029c: 6200 0000 |0000: sget-object v0, L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;.INSTANCE:L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY; // field@0000
//调用myPrint静态方法,并将前一步获取到的$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY.INSTANCE做为参数传给myPrint。
0002a0: 7110 0700 0000 |0002: invoke-static {v0}, LJava8;.myPrint:(LJava8$Logger;)V // method@0007
...
//myPrint方法
#3 : (in LJava8;)
name : 'myPrint'
type : '(LJava8$Logger;)V'
access : 0x000a (PRIVATE STATIC)
...
0002a8: |[0002a8] Java8.myPrint:(LJava8$Logger;)V
// "hello lambda"存放到v0
0002b8: 1a00 1200 |0000: const-string v0, "hello lambda" // string@0012
// 这里v1即为传进来的$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY.INSTANCE,v0为"hello lambda"参数,调用log方法。
0002bc: 7220 0300 0100 |0002: invoke-interface {v1, v0}, LJava8$Logger;.log:(Ljava/lang/String;)V // method@0003
0002c2: 0e00 |0005: return-void
...
//lambda表达式生成的类: $$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY
Class #2 -
Class descriptor : 'L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;'
Access flags : 0x1011 (PUBLIC FINAL SYNTHETIC)
...
// 定一个了INSTANCE静态字段,类型为$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY
Static fields -
#0 : (in L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;)
name : 'INSTANCE'
type : 'L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;'
access : 0x1019 (PUBLIC STATIC FINAL SYNTHETIC)
Instance fields -
// 类初始化方法,初始化INSTANCE字段
Direct methods -
#0 : (in L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;)
name : ''
type : '()V'
access : 0x11008 (STATIC SYNTHETIC CONSTRUCTOR)
...
000208: |[000208] -..Lambda.Java8.QkyWJ8jlAksLjYziID4cZLvHwoY.:()V
// new 一个$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY
000218: 2200 0000 |0000: new-instance v0, L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY; // type@0000
//调用 $$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY初始化函数init
00021c: 7010 0100 0000 |0002: invoke-direct {v0}, L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;.:()V // method@0001
// 将初始化好的$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY对象存放到INSTANCE字段
000222: 6900 0000 |0005: sput-object v0, L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;.INSTANCE:L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY; // field@0000
000226: 0e00 |0007: return-void
....
// 实现log方法
Virtual methods -
#0 : (in L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;)
name : 'log'
type : '(Ljava/lang/String;)V'
access : 0x0011 (PUBLIC FINAL)
...
000240: |[000240] -..Lambda.Java8.QkyWJ8jlAksLjYziID4cZLvHwoY.log:(Ljava/lang/String;)V
000250: 7110 0500 0100 |0000: invoke-static {v1}, LJava8;.lambda$main$0:(Ljava/lang/String;)V // method@0005
....
具体实现已在注释中说明,可以看到d8的实现思路和JDK中的实现思路是一致的,d8只是将JDK在运行时java.lang.invoke.LambdaMetafactory::metafactory
的实现搬到了编译期执行。
参考文献
Android's Java 8 Support
Android 兼容 Java 8 语法特性的原理分析
理解invokedynamic