public class LambdaTest {
public static void main(String[] args) {
Function<String,String> function = str -> str + test1();
String str = test2(function);
System.out.println(str);
}
public static String test1(){
return "学习";
}
public static String test2( Function<String,String> function){
return function.apply("我就");
}
}
最初猜想jdk是通过语法糖, 编译时候, 动态生成了一个Function接口的实现类, 里面进行了接收传入的参数, 并调用
test1()
public class LambdaTest {
public static void main(String[] args) {
Function<String,String> function = new FunctionImpl();
String str = test2(function);
System.out.println(str);
}
public static String test1(){
return "学习";
}
public static String test2( Function<String,String> function){
return function.apply("我就");
}
public class FunctionImpl implements Function<String,String>{
@Override
public String apply(String o) {
return o+test1();
}
}
}
在或者是直接通过, 编译生成匿名内部类的方式, 直接实现调用:
public static void main(String[] args) {
Function<String,String> function = new Function<String, String>() {
@Override
public String apply(String s) {
return s+test1();
}
};
String str = test2(function);
System.out.println(str);
}
public static String test1(){
return "学习";
}
public static String test2( Function<String,String> function){
return function.apply("我就");
}
但是实际上都不是以上的方式。
为什么不使用匿名内部类或者动态实现类呢, 因为有一些缺点:
- 每个匿名内部类都会在
编译时
创建一个对应的class 文件
,在运行时
不可避免的会有加载、验证、准备、解析、初始化等类加载
过程。- 每次调用都会创建一个这个
匿名内部类 class 的实例对象
,无论是有状态的(使用到了外部的变量)还是无状态(没有使用外部变量)的内部类。
运行如下代码, 后看其编译后的字节码源码:
public class LambdaTest {
public static void main(String[] args) {
Function function = s -> s+test1();
String str = test2(function);
System.out.println(str);
}
public static String test1(){
return "学习";
}
public static String test2( Function<String,String> function){
return function.apply("我就");
}
}
如何在其 .class文件目录下 运行命令:
javap -p LambdaMain
输出 class 文件的所有类和成员,得到输出结果:
我们可以看出 Lambda 表达式在 Java 8 中首先会生成一个
私有的静态函数
。
private static java.lang.Object lambda$main$0(java.lang.Object);
当一个方法里面使用多个 Lambda 表达式的时候会在生成的静态函数最后索引数递增。 方法参数和返回值对应函数式接口
private static java.lang.Object lambda$main$1();
private static void lambda$main$2(java.lang.Object);
如果我们定义一个和生成的私有的静态函数一样
public class LambdaTest {
public static void main(String[] args) {
Function function = s -> s+test1();
String str = test2(function);
System.out.println(str);
}
private static Object lambda$main$0(Object s){
return s;
}
public static String test1(){
return "学习";
}
public static String test2( Function<String,String> function){
return function.apply("我就");
}
}
编译时期不会报错, 运行时候会报:
D:\my_project\nettyDemo\src\main\java\com\zhihao\LambdaTest.java:13:27 java: 符号lambda$main$0(java.lang.Object)与com.zhihao.LambdaTest中的 compiler-synthesized 符号冲突
因为存在两个
lambda$main$0
函数, 那么在代码运行的时候,就不知道去调用哪个,因此就会抛错。那么问题, 是谁来调用这个
lambda$main$0
函数
输入命令: javap -c -p LambdaTest
查看更详细的字节码信息
PS D:\my_project\nettyDemo\target\classes\com\zhihao> javap -c -p LambdaTest
警告: 二进制文件LambdaTest包含com.zhihao.LambdaTest
Compiled from "LambdaTest.java"
public class com.zhihao.LambdaTest {
public com.zhihao.LambdaTest();
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 #2, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function;
5: astore_1
6: aload_1
7: invokestatic #3 // Method test2:(Ljava/util/function/Function;)Ljava/lang/String;
10: astore_2
11: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
14: aload_2
15: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
18: return
public static java.lang.String test1();
Code:
0: ldc #6 // String 学习
2: areturn
public static java.lang.String test2(java.util.function.Function<java.lang.String, java.lang.String>);
Code:
0: aload_0
1: ldc #7 // String 我就
3: invokeinterface #8, 2 // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
8: checkcast #9 // class java/lang/String
11: areturn
private static java.lang.Object lambda$main$0(java.lang.Object);
Code:
0: new #10 // class java/lang/StringBuilder
3: dup
4: invokespecial #11 // Method java/lang/StringBuilder."<init>":()V
7: aload_0
8: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
11: invokestatic #13 // Method test1:()Ljava/lang/String;
14: invokevirtual #14 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
20: areturn
}
代码中使用Lambda表达式的语句被编译成
invokedynamic #2, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function;
指向常量池#2位置invokedynamic出现的位置代表一个动态调用点,invokedynamic指令后面会跟一个指向常量池的调用点限定符,这个限定符会被解析为一个动态调用点。依据这个可以找到对应的动态调用引导方法java.lang.invoke.CallSite。
详细情况 JVM 虚拟机规范
它被用于延迟Lambda表达式到字节码的转换,最终这一操作被推迟到了运行时。换句话说,以这种方式使用invokedynamic,可以将实现Lambda表达式的这部分代码的字节码生成推迟到运行时。这种设计选择带来了一系列好结果。
LambdaMetafactory # metafactory() (文档)
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
// 在这个函数中可以发现为Lambda表达式生成了一个内部类
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
// 验证
mf.validateMetafactoryArgs();
return mf.buildCallSite();
}
在使用到 表达式的地方 打上断点, 然后在上面类对应方法中再打上断点就可以调试看了!
参数: caller、invokedName、invokedType都由jvm生成,
invokedName和invokedType分别是由动态调用点的NameAndType生成,
samMethodType和instantiatedMethodType表示该函数式接口的方法描述符,
implMethod最为重要,指用户实现的Lambda方法,本例为:com.zhihao.LambdaTest.lambda$main$0(Object)Object/invokeStatic
最终通过内存生成的内部类, 调用
lambda$main$0
方法
// 内部类
final class Lambda$1 {
@Override
public R apply(T t) {
return lambda$main$0(t);
}
}
私有的静态函数
,JDK 最终使用 invokedynamic 字节码指令调用。1