lambda 表达式的原理分为两部分:
(1)编译时 Javac 编译器的语法糖;
(2)实际调用时 invokeDynamic 动态调用;
下面分别解释一下。
一、测试例子
ppublic class LambdaTest {
/**
* 说明:
* 类里定义了几个函数式接口的变量,以及几个 “testCallLambdaMethodName数字” 命名方法,这些方法内部使用了函数式接口
* 这里函数式接口统一通过 lambda 表达式来使用
* 它这些变量和方法用于测试有使用 lambda 表达式时,Javac 编译器自动生成 private 方法,默认是 static 方法, 如果 lambda 表达式执行体中有访问实例属性,则为实例方法。没有方法名是如何命名的
* 具体看下面的结论
*/
private TestFunction1<String> filedTestFunction1 = (str) -> ".................";
private static TestFunction1<String> staticTestFunction1 = (str) -> ".................";
private static final TestFunction2<String> constTestFunction2 = (str1, str2) -> ".................";
public void testCallLambdaMethodName0() {
TestFunction1<String> testFunction1 = (str) -> ".................";
testFunction1.execute(" ");
}
public static void testCallLambdaMethodName1() {
TestFunction2<String> testFunction2 = (str1, str2) -> ".................";
testFunction2.execute(" ", " ");
}
public static void main(String[] args) {
String input = "input param";
String s = execute(input, (str) -> {
System.out.println("TestFunction1 执行");
System.out.println("输入:" + str);
return "TestFunction1 execute success";
});
System.out.println("结果:");
System.out.println("----------------------------");
String input1 = "input param1";
String input2 = "input param2";
String s2 = execute1(input1, input2, (str1, str2) -> {
System.out.println("TestFunction2 执行");
System.out.println("输入:" + str1 + " " + str2);
return "TestFunction2 execute success";
});
System.out.println("结果:" + s2);
}
public void testCallLambdaMethodName2() {
TestFunction1<String> testFunction1 = (str) -> ".................";
testFunction1.execute(" ");
}
public static String execute(String s, TestFunction1<String> testFunction1) {
return testFunction1.execute(s);
}
public static String execute1(String s, String s1, TestFunction2<String> testFunction2) {
return testFunction2.execute(s, s1);
}
/**
* TestFunction1,execute 方法一个参数
*
* @param <T>
*/
@FunctionalInterface
private interface TestFunction1<T> {
String execute(T t1);
}
/**
* TestFunction2,execute 方法两个参数
*
* @param <T>
*/
@FunctionalInterface
private interface TestFunction2<T> {
String execute(T t1, T t2);
}
}
二、编译时原理
Java 8 下 Idea 自带的反编译工具、jad 这些都不好用,推荐用 cfr。
下载地址:http://www.benf.org/other/cfr/
$ java -jar cfr-0.149.jar LambdaTest.class --decodelambdas false
/*
* Decompiled with CFR 0.149.
*/
package lll;
import java.lang.invoke.LambdaMetafactory;
public class LambdaTest {
private TestFunction1<String> filedTestFunction1 = (TestFunction1<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Ljava/lang/String
;, lambda$new$0(java.lang.String ), (Ljava/lang/String;)Ljava/lang/String;)();
private static TestFunction1<String> staticTestFunction1 = (TestFunction1<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Ljava/lan
g/String;, lambda$static$1(java.lang.String ), (Ljava/lang/String;)Ljava/lang/String;)();
private static final TestFunction2<String> constTestFunction2 = (TestFunction2<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;Ljava
/lang/Object;)Ljava/lang/String;, lambda$static$2(java.lang.String java.lang.String ), (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;)();
public void testCallLambdaMethodName0() {
TestFunction1<String> testFunction1 = (TestFunction1<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Ljava/lang/String;, lambda
$testCallLambdaMethodName0$3(java.lang.String ), (Ljava/lang/String;)Ljava/lang/String;)();
testFunction1.execute(" ");
}
public static void testCallLambdaMethodName1() {
TestFunction2<String> testFunction2 = (TestFunction2<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;Ljava/lang/Object;)Ljava/la
ng/String;, lambda$testCallLambdaMethodName1$4(java.lang.String java.lang.String ), (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;)();
testFunction2.execute(" ", " ");
}
public static void main(String[] args) {
String input = "input param";
String s = LambdaTest.execute(input, (TestFunction1<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Ljava/lang/String;, lambda$
main$5(java.lang.String ), (Ljava/lang/String;)Ljava/lang/String;)());
System.out.println("\u7ed3\u679c\uff1a" + s);
System.out.println("----------------------------");
String input1 = "input param1";
String input2 = "input param2";
String s2 = LambdaTest.execute1(input1, input2, (TestFunction2<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;Ljava/lang/Object
;)Ljava/lang/String;, lambda$main$6(java.lang.String java.lang.String ), (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;)());
System.out.println("\u7ed3\u679c\uff1a" + s2);
}
public void testCallLambdaMethodName2() {
TestFunction1<String> testFunction1 = (TestFunction1<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Ljava/lang/String;, lambda
$testCallLambdaMethodName2$7(java.lang.String ), (Ljava/lang/String;)Ljava/lang/String;)();
testFunction1.execute(" ");
}
public static String execute(String s, TestFunction1<String> testFunction1) {
return testFunction1.execute(s);
}
public static String execute1(String s, String s1, TestFunction2<String> testFunction2) {
return testFunction2.execute(s, s1);
}
private static /* synthetic */ String lambda$testCallLambdaMethodName2$7(String str) {
return ".................";
}
private static /* synthetic */ String lambda$main$6(String str1, String str2) {
System.out.println("TestFunction2 \u6267\u884c");
System.out.println("\u8f93\u5165\uff1a" + str1 + " " + str2);
return "TestFunction2 execute success";
}
private static /* synthetic */ String lambda$main$5(String str) {
System.out.println("TestFunction1 \u6267\u884c");
System.out.println("\u8f93\u5165\uff1a" + str);
return "TestFunction1 execute success";
}
private static /* synthetic */ String lambda$testCallLambdaMethodName1$4(String str1, String str2) {
return ".................";
}
private static /* synthetic */ String lambda$testCallLambdaMethodName0$3(String str) {
return ".................";
}
private static /* synthetic */ String lambda$static$2(String str1, String str2) {
return ".................";
}
private static /* synthetic */ String lambda$static$1(String str) {
return ".................";
}
private static /* synthetic */ String lambda$new$0(String str) {
return ".................";
}
@FunctionalInterface
private static interface TestFunction2<T> {
public String execute(T var1, T var2);
}
@FunctionalInterface
private static interface TestFunction1<T> {
public String execute(T var1);
}
}
- 编译时原理结论1:
lambda 表达式是一个语法糖,Javac 编译器会为每个使用 lambda 表达式的地方生成一个对应的 private static 方法,包括变量声明以及方法体中这些位置。
生成的这些方法的方法体就是对应 lambda 表达式的执行体。
生成的方法命名规则:
lambda$使用 lambda 表达式的方法名$整个类中使用 lambda 表达式的顺序序号,各部分含义:
(1)lambda 是固定的。
(2)使用 lambda 表达式的方法名:如果是方法体中使用的,那么就是对应的方法名;如果是实例变量,是 new;如果是类变量,是 static。
(3)整个类中使用 lambda 表达式的顺序序号:从 0 开始,按照类中使用 lambda 表达式的地方依次自增来编号。 - 编译时原理结论2:
所有使用 lambda 表达式的地方都替换为了调用LambdaMetafactory.metafactory
方法,该方法传入的MethodHandle
对象参数就是与 Javac 编译器为 lambda 表达式自动生成的方法一一对应的,方法返回的CallSite
对象。
接着再看下反编译后的详细输出:
$ javap -verbose LambdaTest.class
Classfile /D:/Study/idea/IdeaProjects/demo/target/classes/lll/LambdaTest.class
Last modified 2020-4-24; size 4674 bytes
MD5 checksum 22ee663ec7839be21f73b51413d35ff3
Compiled from "LambdaTest.java"
public class lll.LambdaTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #36.#97 // java/lang/Object."<init>":()V
#2 = InvokeDynamic #0:#103 // #0:execute:()Llll/LambdaTest$TestFunction1;
#3 = Fieldref #35.#104 // lll/LambdaTest.filedTestFunction1:Llll/LambdaTest$TestFunction1;
#4 = InvokeDynamic #1:#103 // #1:execute:()Llll/LambdaTest$TestFunction1;
#5 = String #106 //
#6 = InterfaceMethodref #40.#107 // lll/LambdaTest$TestFunction1.execute:(Ljava/lang/Object;)Ljava/lang/String;
#7 = InvokeDynamic #2:#111 // #2:execute:()Llll/LambdaTest$TestFunction2;
#8 = InterfaceMethodref #37.#112 // lll/LambdaTest$TestFunction2.execute:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;
#9 = String #113 // input param
#10 = InvokeDynamic #3:#103 // #3:execute:()Llll/LambdaTest$TestFunction1;
#11 = Methodref #35.#115 // lll/LambdaTest.execute:(Ljava/lang/String;Llll/LambdaTest$TestFunction1;)Ljava/lang/String;
#12 = Fieldref #116.#117 // java/lang/System.out:Ljava/io/PrintStream;
#13 = String #118 // 结果:
#14 = Methodref #119.#120 // java/io/PrintStream.println:(Ljava/lang/String;)V
#15 = String #121 // ----------------------------
#16 = String #122 // input param1
#17 = String #123 // input param2
#18 = InvokeDynamic #4:#111 // #4:execute:()Llll/LambdaTest$TestFunction2;
#19 = Methodref #35.#125 // lll/LambdaTest.execute1:(Ljava/lang/String;Ljava/lang/String;Llll/LambdaTest$TestFunction2;)Ljava/lang/String;
#20 = Class #126 // java/lang/StringBuilder
#21 = Methodref #20.#97 // java/lang/StringBuilder."<init>":()V
#22 = Methodref #20.#127 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#23 = Methodref #20.#128 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#24 = InvokeDynamic #5:#103 // #5:execute:()Llll/LambdaTest$TestFunction1;
#25 = String #130 // .................
#26 = String #131 // TestFunction2 执行
#27 = String #132 // 输入:
#28 = String #133 // TestFunction2 execute success
#29 = String #134 // TestFunction1 执行
#30 = String #135 // TestFunction1 execute success
#31 = InvokeDynamic #6:#103 // #6:execute:()Llll/LambdaTest$TestFunction1;
#32 = Fieldref #35.#137 // lll/LambdaTest.staticTestFunction1:Llll/LambdaTest$TestFunction1;
#33 = InvokeDynamic #7:#111 // #7:execute:()Llll/LambdaTest$TestFunction2;
#34 = Fieldref #35.#139 // lll/LambdaTest.constTestFunction2:Llll/LambdaTest$TestFunction2;
#35 = Class #140 // lll/LambdaTest
#36 = Class #141 // java/lang/Object
#37 = Class #142 // lll/LambdaTest$TestFunction2
#38 = Utf8 TestFunction2
#39 = Utf8 InnerClasses
#40 = Class #143 // lll/LambdaTest$TestFunction1
#41 = Utf8 TestFunction1
#42 = Utf8 filedTestFunction1
#43 = Utf8 Llll/LambdaTest$TestFunction1;
#44 = Utf8 Signature
#45 = Utf8 Llll/LambdaTest$TestFunction1<Ljava/lang/String;>;
#46 = Utf8 staticTestFunction1
#47 = Utf8 constTestFunction2
#48 = Utf8 Llll/LambdaTest$TestFunction2;
#49 = Utf8 Llll/LambdaTest$TestFunction2<Ljava/lang/String;>;
#50 = Utf8 <init>
#51 = Utf8 ()V
#52 = Utf8 Code
#53 = Utf8 LineNumberTable
#54 = Utf8 LocalVariableTable
#55 = Utf8 this
#56 = Utf8 Llll/LambdaTest;
#57 = Utf8 testCallLambdaMethodName0
#58 = Utf8 testFunction1
#59 = Utf8 LocalVariableTypeTable
#60 = Utf8 testCallLambdaMethodName1
#61 = Utf8 testFunction2
#62 = Utf8 main
#63 = Utf8 ([Ljava/lang/String;)V
#64 = Utf8 args
#65 = Utf8 [Ljava/lang/String;
#66 = Utf8 input
#67 = Utf8 Ljava/lang/String;
#68 = Utf8 s
#69 = Utf8 input1
#70 = Utf8 input2
#71 = Utf8 s2
#72 = Utf8 MethodParameters
#73 = Utf8 testCallLambdaMethodName2
#74 = Utf8 execute
#75 = Utf8 (Ljava/lang/String;Llll/LambdaTest$TestFunction1;)Ljava/lang/String;
#76 = Utf8 (Ljava/lang/String;Llll/LambdaTest$TestFunction1<Ljava/lang/String;>;)Ljava/lang/String;
#77 = Utf8 execute1
#78 = Utf8 (Ljava/lang/String;Ljava/lang/String;Llll/LambdaTest$TestFunction2;)Ljava/lang/String;
#79 = Utf8 s1
#80 = Utf8 (Ljava/lang/String;Ljava/lang/String;Llll/LambdaTest$TestFunction2<Ljava/lang/String;>;)Ljava/lang/String;
#81 = Utf8 lambda$testCallLambdaMethodName2$7
#82 = Utf8 (Ljava/lang/String;)Ljava/lang/String;
#83 = Utf8 str
#84 = Utf8 lambda$main$6
#85 = Utf8 (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#86 = Utf8 str1
#87 = Utf8 str2
#88 = Utf8 lambda$main$5
#89 = Utf8 lambda$testCallLambdaMethodName1$4
#90 = Utf8 lambda$testCallLambdaMethodName0$3
#91 = Utf8 lambda$static$2
#92 = Utf8 lambda$static$1
#93 = Utf8 lambda$new$0
#94 = Utf8 <clinit>
#95 = Utf8 SourceFile
#96 = Utf8 LambdaTest.java
#97 = NameAndType #50:#51 // "<init>":()V
#98 = Utf8 BootstrapMethods
#99 = MethodHandle #6:#144 // 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;
#100 = MethodType #145 // (Ljava/lang/Object;)Ljava/lang/String;
#101 = MethodHandle #6:#146 // invokestatic lll/LambdaTest.lambda$new$0:(Ljava/lang/String;)Ljava/lang/String;
#102 = MethodType #82 // (Ljava/lang/String;)Ljava/lang/String;
#103 = NameAndType #74:#147 // execute:()Llll/LambdaTest$TestFunction1;
#104 = NameAndType #42:#43 // filedTestFunction1:Llll/LambdaTest$TestFunction1;
#105 = MethodHandle #6:#148 // invokestatic lll/LambdaTest.lambda$testCallLambdaMethodName0$3:(Ljava/lang/String;)Ljava/lang/String;
#106 = Utf8
#107 = NameAndType #74:#145 // execute:(Ljava/lang/Object;)Ljava/lang/String;
#108 = MethodType #149 // (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;
#109 = MethodHandle #6:#150 // invokestatic lll/LambdaTest.lambda$testCallLambdaMethodName1$4:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String
;
#110 = MethodType #85 // (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#111 = NameAndType #74:#151 // execute:()Llll/LambdaTest$TestFunction2;
#112 = NameAndType #74:#149 // execute:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;
#113 = Utf8 input param
#114 = MethodHandle #6:#152 // invokestatic lll/LambdaTest.lambda$main$5:(Ljava/lang/String;)Ljava/lang/String;
#115 = NameAndType #74:#75 // execute:(Ljava/lang/String;Llll/LambdaTest$TestFunction1;)Ljava/lang/String;
#116 = Class #153 // java/lang/System
#117 = NameAndType #154:#155 // out:Ljava/io/PrintStream;
#118 = Utf8 结果:
#119 = Class #156 // java/io/PrintStream
#120 = NameAndType #157:#158 // println:(Ljava/lang/String;)V
#121 = Utf8 ----------------------------
#122 = Utf8 input param1
#123 = Utf8 input param2
#124 = MethodHandle #6:#159 // invokestatic lll/LambdaTest.lambda$main$6:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#125 = NameAndType #77:#78 // execute1:(Ljava/lang/String;Ljava/lang/String;Llll/LambdaTest$TestFunction2;)Ljava/lang/String;
#126 = Utf8 java/lang/StringBuilder
#127 = NameAndType #160:#161 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#128 = NameAndType #162:#163 // toString:()Ljava/lang/String;
#129 = MethodHandle #6:#164 // invokestatic lll/LambdaTest.lambda$testCallLambdaMethodName2$7:(Ljava/lang/String;)Ljava/lang/String;
#130 = Utf8 .................
#131 = Utf8 TestFunction2 执行
#132 = Utf8 输入:
#133 = Utf8 TestFunction2 execute success
#134 = Utf8 TestFunction1 执行
#135 = Utf8 TestFunction1 execute success
#136 = MethodHandle #6:#165 // invokestatic lll/LambdaTest.lambda$static$1:(Ljava/lang/String;)Ljava/lang/String;
#137 = NameAndType #46:#43 // staticTestFunction1:Llll/LambdaTest$TestFunction1;
#138 = MethodHandle #6:#166 // invokestatic lll/LambdaTest.lambda$static$2:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#139 = NameAndType #47:#48 // constTestFunction2:Llll/LambdaTest$TestFunction2;
#140 = Utf8 lll/LambdaTest
#141 = Utf8 java/lang/Object
#142 = Utf8 lll/LambdaTest$TestFunction2
#143 = Utf8 lll/LambdaTest$TestFunction1
#144 = Methodref #167.#168 // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/in
voke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#145 = Utf8 (Ljava/lang/Object;)Ljava/lang/String;
#146 = Methodref #35.#169 // lll/LambdaTest.lambda$new$0:(Ljava/lang/String;)Ljava/lang/String;
#147 = Utf8 ()Llll/LambdaTest$TestFunction1;
#148 = Methodref #35.#170 // lll/LambdaTest.lambda$testCallLambdaMethodName0$3:(Ljava/lang/String;)Ljava/lang/String;
#149 = Utf8 (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;
#150 = Methodref #35.#171 // lll/LambdaTest.lambda$testCallLambdaMethodName1$4:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#151 = Utf8 ()Llll/LambdaTest$TestFunction2;
#152 = Methodref #35.#172 // lll/LambdaTest.lambda$main$5:(Ljava/lang/String;)Ljava/lang/String;
#153 = Utf8 java/lang/System
#154 = Utf8 out
#155 = Utf8 Ljava/io/PrintStream;
#156 = Utf8 java/io/PrintStream
#157 = Utf8 println
#158 = Utf8 (Ljava/lang/String;)V
#159 = Methodref #35.#173 // lll/LambdaTest.lambda$main$6:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#160 = Utf8 append
#161 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#162 = Utf8 toString
#163 = Utf8 ()Ljava/lang/String;
#164 = Methodref #35.#174 // lll/LambdaTest.lambda$testCallLambdaMethodName2$7:(Ljava/lang/String;)Ljava/lang/String;
#165 = Methodref #35.#175 // lll/LambdaTest.lambda$static$1:(Ljava/lang/String;)Ljava/lang/String;
#166 = Methodref #35.#176 // lll/LambdaTest.lambda$static$2:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#167 = Class #177 // java/lang/invoke/LambdaMetafactory
#168 = NameAndType #178:#181 // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/M
ethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#169 = NameAndType #93:#82 // lambda$new$0:(Ljava/lang/String;)Ljava/lang/String;
#170 = NameAndType #90:#82 // lambda$testCallLambdaMethodName0$3:(Ljava/lang/String;)Ljava/lang/String;
#171 = NameAndType #89:#85 // lambda$testCallLambdaMethodName1$4:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#172 = NameAndType #88:#82 // lambda$main$5:(Ljava/lang/String;)Ljava/lang/String;
#173 = NameAndType #84:#85 // lambda$main$6:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#174 = NameAndType #81:#82 // lambda$testCallLambdaMethodName2$7:(Ljava/lang/String;)Ljava/lang/String;
#175 = NameAndType #92:#82 // lambda$static$1:(Ljava/lang/String;)Ljava/lang/String;
#176 = NameAndType #91:#85 // lambda$static$2:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#177 = Utf8 java/lang/invoke/LambdaMetafactory
#178 = Utf8 metafactory
#179 = Class #183 // java/lang/invoke/MethodHandles$Lookup
#180 = Utf8 Lookup
#181 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/M
ethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#182 = Class #184 // java/lang/invoke/MethodHandles
#183 = Utf8 java/lang/invoke/MethodHandles$Lookup
#184 = Utf8 java/lang/invoke/MethodHandles
{
public lll.LambdaTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: invokedynamic #2, 0 // InvokeDynamic #0:execute:()Llll/LambdaTest$TestFunction1;
10: putfield #3 // Field filedTestFunction1:Llll/LambdaTest$TestFunction1;
13: return
LineNumberTable:
line 3: 0
line 13: 4
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 this Llll/LambdaTest;
public void testCallLambdaMethodName0();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: invokedynamic #4, 0 // InvokeDynamic #1:execute:()Llll/LambdaTest$TestFunction1;
5: astore_1
6: aload_1
7: ldc #5 // String
9: invokeinterface #6, 2 // InterfaceMethod lll/LambdaTest$TestFunction1.execute:(Ljava/lang/Object;)Ljava/lang/String;
14: pop
15: return
LineNumberTable:
line 20: 0
line 21: 6
line 22: 15
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this Llll/LambdaTest;
6 10 1 testFunction1 Llll/LambdaTest$TestFunction1;
LocalVariableTypeTable:
Start Length Slot Name Signature
6 10 1 testFunction1 Llll/LambdaTest$TestFunction1<Ljava/lang/String;>;
public static void testCallLambdaMethodName1();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=1, args_size=0
0: invokedynamic #7, 0 // InvokeDynamic #2:execute:()Llll/LambdaTest$TestFunction2;
5: astore_0
6: aload_0
7: ldc #5 // String
9: ldc #5 // String
11: invokeinterface #8, 3 // InterfaceMethod lll/LambdaTest$TestFunction2.execute:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;
16: pop
17: return
LineNumberTable:
line 25: 0
line 26: 6
line 27: 17
LocalVariableTable:
Start Length Slot Name Signature
6 12 0 testFunction2 Llll/LambdaTest$TestFunction2;
LocalVariableTypeTable:
Start Length Slot Name Signature
6 12 0 testFunction2 Llll/LambdaTest$TestFunction2<Ljava/lang/String;>;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=6, args_size=1
0: ldc #9 // String input param
2: astore_1
3: aload_1
4: invokedynamic #10, 0 // InvokeDynamic #3:execute:()Llll/LambdaTest$TestFunction1;
9: invokestatic #11 // Method execute:(Ljava/lang/String;Llll/LambdaTest$TestFunction1;)Ljava/lang/String;
12: astore_2
13: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
16: ldc #13 // String 结果:
18: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
21: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
24: ldc #15 // String ----------------------------
26: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
29: ldc #16 // String input param1
31: astore_3
32: ldc #17 // String input param2
34: astore 4
36: aload_3
37: aload 4
39: invokedynamic #18, 0 // InvokeDynamic #4:execute:()Llll/LambdaTest$TestFunction2;
44: invokestatic #19 // Method execute1:(Ljava/lang/String;Ljava/lang/String;Llll/LambdaTest$TestFunction2;)Ljava/lang/String;
47: astore 5
49: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
52: new #20 // class java/lang/StringBuilder
55: dup
56: invokespecial #21 // Method java/lang/StringBuilder."<init>":()V
59: ldc #13 // String 结果:
61: invokevirtual #22 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
64: aload 5
66: invokevirtual #22 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
69: invokevirtual #23 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
72: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
75: return
LineNumberTable:
line 30: 0
line 31: 3
line 36: 13
line 38: 21
line 40: 29
line 41: 32
line 42: 36
line 47: 49
line 48: 75
LocalVariableTable:
Start Length Slot Name Signature
0 76 0 args [Ljava/lang/String;
3 73 1 input Ljava/lang/String;
13 63 2 s Ljava/lang/String;
32 44 3 input1 Ljava/lang/String;
36 40 4 input2 Ljava/lang/String;
49 27 5 s2 Ljava/lang/String;
MethodParameters:
Name Flags
args
public void testCallLambdaMethodName2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: invokedynamic #24, 0 // InvokeDynamic #5:execute:()Llll/LambdaTest$TestFunction1;
5: astore_1
6: aload_1
7: ldc #5 // String
9: invokeinterface #6, 2 // InterfaceMethod lll/LambdaTest$TestFunction1.execute:(Ljava/lang/Object;)Ljava/lang/String;
14: pop
15: return
LineNumberTable:
line 51: 0
line 52: 6
line 53: 15
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this Llll/LambdaTest;
6 10 1 testFunction1 Llll/LambdaTest$TestFunction1;
LocalVariableTypeTable:
Start Length Slot Name Signature
6 10 1 testFunction1 Llll/LambdaTest$TestFunction1<Ljava/lang/String;>;
public static java.lang.String execute(java.lang.String, lll.LambdaTest$TestFunction1<java.lang.String>);
descriptor: (Ljava/lang/String;Llll/LambdaTest$TestFunction1;)Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=2
0: aload_1
1: aload_0
2: invokeinterface #6, 2 // InterfaceMethod lll/LambdaTest$TestFunction1.execute:(Ljava/lang/Object;)Ljava/lang/String;
7: areturn
LineNumberTable:
line 56: 0
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 s Ljava/lang/String;
0 8 1 testFunction1 Llll/LambdaTest$TestFunction1;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 8 1 testFunction1 Llll/LambdaTest$TestFunction1<Ljava/lang/String;>;
MethodParameters:
Name Flags
s
testFunction1
Signature: #76 // (Ljava/lang/String;Llll/LambdaTest$TestFunction1<Ljava/lang/String;>;)Ljava/lang/String;
public static java.lang.String execute1(java.lang.String, java.lang.String, lll.LambdaTest$TestFunction2<java.lang.String>);
descriptor: (Ljava/lang/String;Ljava/lang/String;Llll/LambdaTest$TestFunction2;)Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=3
0: aload_2
1: aload_0
2: aload_1
3: invokeinterface #8, 3 // InterfaceMethod lll/LambdaTest$TestFunction2.execute:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;
8: areturn
LineNumberTable:
line 60: 0
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 s Ljava/lang/String;
0 9 1 s1 Ljava/lang/String;
0 9 2 testFunction2 Llll/LambdaTest$TestFunction2;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 9 2 testFunction2 Llll/LambdaTest$TestFunction2<Ljava/lang/String;>;
MethodParameters:
Name Flags
s
s1
testFunction2
Signature: #80 // (Ljava/lang/String;Ljava/lang/String;Llll/LambdaTest$TestFunction2<Ljava/lang/String;>;)Ljava/lang/String;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: invokedynamic #31, 0 // InvokeDynamic #6:execute:()Llll/LambdaTest$TestFunction1;
5: putstatic #32 // Field staticTestFunction1:Llll/LambdaTest$TestFunction1;
8: invokedynamic #33, 0 // InvokeDynamic #7:execute:()Llll/LambdaTest$TestFunction2;
13: putstatic #34 // Field constTestFunction2:Llll/LambdaTest$TestFunction2;
16: return
LineNumberTable:
line 15: 0
line 17: 8
}
SourceFile: "LambdaTest.java"
InnerClasses:
public static final #180= #179 of #182; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #99 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/l
ang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#100 (Ljava/lang/Object;)Ljava/lang/String;
#101 invokestatic lll/LambdaTest.lambda$new$0:(Ljava/lang/String;)Ljava/lang/String;
#102 (Ljava/lang/String;)Ljava/lang/String;
1: #99 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/l
ang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#100 (Ljava/lang/Object;)Ljava/lang/String;
#105 invokestatic lll/LambdaTest.lambda$testCallLambdaMethodName0$3:(Ljava/lang/String;)Ljava/lang/String;
#102 (Ljava/lang/String;)Ljava/lang/String;
2: #99 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/l
ang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#108 (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;
#109 invokestatic lll/LambdaTest.lambda$testCallLambdaMethodName1$4:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#110 (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
3: #99 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/l
ang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#100 (Ljava/lang/Object;)Ljava/lang/String;
#114 invokestatic lll/LambdaTest.lambda$main$5:(Ljava/lang/String;)Ljava/lang/String;
#102 (Ljava/lang/String;)Ljava/lang/String;
4: #99 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/l
ang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#108 (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;
#124 invokestatic lll/LambdaTest.lambda$main$6:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#110 (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
5: #99 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/l
ang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#100 (Ljava/lang/Object;)Ljava/lang/String;
#129 invokestatic lll/LambdaTest.lambda$testCallLambdaMethodName2$7:(Ljava/lang/String;)Ljava/lang/String;
#102 (Ljava/lang/String;)Ljava/lang/String;
6: #99 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/l
ang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#100 (Ljava/lang/Object;)Ljava/lang/String;
#136 invokestatic lll/LambdaTest.lambda$static$1:(Ljava/lang/String;)Ljava/lang/String;
#102 (Ljava/lang/String;)Ljava/lang/String;
7: #99 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/l
ang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#108 (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;
#138 invokestatic lll/LambdaTest.lambda$static$2:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#110 (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
编译时原理结论3:
- 所有使用
lambda
表达式的地方都会生成一条invokeDynamic
字节码指令,也就是lambda
式执行时是动态调用的。
二、运行时调用原理
以 main 方法的第一个 lambda 表达式调用为例:
1、invokedynamic
指令调用
4: invokedynamic #10, 0 // InvokeDynamic #3:execute:()Llll/LambdaTest$TestFunction1;
invokedynamic
指令有两个参数:
参数1:常量池中某个 CONSTANT_InvokeDynamic_info
类型常量的索引值
参数2:固定为 0,就是一个占位符。
我们这里只关注第 1 个参数。
CONSTANT_InvokeDynamic_info
常量:
CONSTANT_InvokeDynamic_info {
// 标志位
u1 tag;
// 保存了整个 class 文件的属性表的 BootStrapMethods 属性的数组中某项引导方法的索引值
u2 bootstrap_method_attr_index;
// 保存了常量池中某个 `CONSTANT_NameAndType_info` 类型常量的索引值
u2 name_and_type_index;
}
这个例子就是第 10 项常量:
#10 = InvokeDynamic #3:#103 // #3:execute:()Llll/LambdaTest$TestFunction1;
这个 CONSTANT_InvokeDynamic_info
常量的值是 #3:#103。
(1)根据 #3 到 class 文件属性表的 BootstrapMethods 的数组中找到索引为 3 的引导方法:
// 3 就是索引值
3: #99 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/l
ang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#100 (Ljava/lang/Object;)Ljava/lang/String;
#114 invokestatic lll/LambdaTest.lambda$main$5:(Ljava/lang/String;)Ljava/lang/String;
#102 (Ljava/lang/String;)Ljava/lang/String;
(2)根据 #103 到常量池中找到CONSTANT_NameAndType_info
类型的常量:
#103 = NameAndType #74:#147 // execute:()Llll/LambdaTest$TestFunction1;
从这个常量的值中可以获取到方法名称和描述符, 即后面输出的 execute:()Llll/LambdaTest$TestFunction1;
注意:根据这个输出可以发现其中的 name
对应的 execute
是方法名,但后面的 type
对应的 ()Llll/LambdaTest$TestFunction1
并不是正确的方法描述符,正确的方法描述符应该是参数列表后面跟着返回值,对于 String execute(T t1)
,具体 Lambda
表达式实现函数式接口 TestFunction1
时指定了泛型 T
是 String
,因此它的正确描述符应该是 (Ljava/lang/String;)Ljava/lang/String
,但这里输出的却是 ()Llll/LambdaTest$TestFunction1
,()
后面的是这个方法所属的类的全限定名。这里知道涉及到 Lambda
表达式的函数式接口的方法对应的 NameAndType
类型常量的输出和普通方法对应的NameAndType
类型常量的输出有一些区别即可,具体为什么会这样,以后有时间再研究。
关于涉及到泛型的方法描述符参考另一篇文章。
2、引导方法 BootstrapMethod
BootstrapMethods
属性:
BootstrapMethods_attribute {
u2 attribute_name_index; // 常量池中属性名称的索引
u4 attribute_length; // 属性占用长度,字节
u2 num_bootstrap_methods; // 引导方法数量
// 每个引导方法组成
{ u2 bootstrap_method_ref; // 保存了常量池中某个 `CONSTANT_MethodHandle_info` 类型的常量
u2 num_bootstrap_arguments; // 引导方法的参数个数
u2 bootstrap_arguments[num_bootstrap_arguments]; // 引导方法参数数组,数组的每一项都是常量池中的对应参数类型的常量的索引值
} bootstrap_methods[num_bootstrap_methods]; // 引导方法数组
}
重点看引导方法的 bootstrap_method_ref
。
// 3 就是索引值
3: #99 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/l
ang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#100 (Ljava/lang/Object;)Ljava/lang/String;
#114 invokestatic lll/LambdaTest.lambda$main$5:(Ljava/lang/String;)Ljava/lang/String;
#102 (Ljava/lang/String;)Ljava/lang/String;
CONSTANT_MethodHandle_info
常量:
CONSTANT_MethodHandle_info {
// 标志位
u1 tag;
// 方法句柄的类型,1 ~ 9,参见下表 reference_kind 列的值
u1 reference_kind;
// 保存了常量池某项下面表中 constant_pool_entry 列中类型常量的索引值
u2 reference_index;
}
constant_pool_entry | reference_kind |
---|---|
CONSTANT_Fieldref_info | 1 (REF_getField), 2 (REF_getStatic), 3 (REF_putField), 4 (REF_putStatic) |
CONSTANT_Methodref_info | 5 (REF_invokeVirtual), 6 (REF_invokeStatic), 7 (REF_invokeSpecial), 8 (REF_newInvokeSpecial) |
CONSTANT_InterfaceMethodref_info | 9 (REF_invokeInterface) |
从该表中可以看出,一个常量类型可能对应多个方法句柄类型,因此 reference_index 只是常量索引,方法句柄类型还需要看 reference_kind。
可以理解为方法句柄类型就是实际操作常量类型的各种方法调用对应的字节码指令。
这个例子就是第 99项常量:
#99 = MethodHandle #6:#144 // 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;
这个 CONSTANT_MethodHandle_info
常量的值是 #6:#144 。
(1)根据 #6 到上边中确定方法句柄类型为 REF_invokeStatic
;
(2)根据 #144 到常量池中找到 CONSTANT_Methodref_info
类型的常量:
#144 = Methodref #167.#168 // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/in
voke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
所以,可以看到最终调用了 LambdaMetaFactory.metafactory
方法,这个很重要:
// 返回值:CallSite 对象表示真正要执行的目标方法调用
public static CallSite metafactory(
// 代表查找上下文与调用者的访问权限, 调用invokedynamic 指令时, JVM 会自动为这个参数传入值
MethodHandles.Lookup caller,
// 调用方法名称, 调用 invokedynamic 时, JVM 会自动为这个参数传入值,传入的值就是 invokedynamic 指令的第二个参数 CONSTANT_InvokeDynamic_info 中指向的 CONSTANT_NameAndType_info 对应的方法名称
String invokedName,
// 调用的方法类型,只根据方法参数类型和返回值类型确定,调用 invokedynamic 时, JVM 会自动为这个参数传入值,传入的值就是和上一个参数获取方式类似,CONSTANT_NameAndType_info 对应的方法描述符
MethodType invokedType,
// 函数接口方法类型,如果有泛型会被擦除
MethodType samMethodType,
// 方法句柄,表示调用时将被执行的具体函数接口实现方法
MethodHandle implMethod,
// 函数接口方法类型, 当方法没有泛型时和 samMethodType 一样, 有泛型时会保留泛型信息
MethodType instantiatedMethodType
)
其中后 3 个参数均是引导方法 bootstrap_arguments[num_bootstrap_arguments]
的值:
#100 = MethodType #145 // (Ljava/lang/Object;)Ljava/lang/String;
#114 = MethodHandle #6:#152 // invokestatic lll/LambdaTest.lambda$main$5:(Ljava/lang/String;)Ljava/lang/String;
#102 = MethodType #82 // (Ljava/lang/String;)Ljava/lang/String;
从这里可以看出真正调用的是 LambdaTest.lambda$main$5
这个方法。
这个方法通过编译时可以知道是由 Javac 编译器自动生成的。
看一下 LambdaMetaFactory.metafactory
方法的实现:
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();
}
(1)创建了一个 InnerClassLambdaMetafactory
对象;
(2)调用buildCallSite
方法返回一个 CallSite 对象。具体来说,在 buildCallSite
方法中先调用 spinInnerClass
方法,它通过 ASM
创建一个实现对应函数式接口的匿名内部类,使用 ClassWriter
直接生成 class 的 byte[]。默认不会将这个内部类的 class 文件输出到磁盘,只有设置 -Djdk.internal.lambda.dumpProxyClasses
时才会输出 class 文件到系统的项目路径/包名下,并为这个内部类进行命名,命名规则:
类名$$Lambda$lambda 表达式的调用顺序序号(从 1 开始)
所以只有在实际调用 lambda 表达式时才会为它生成内部类,仅使用了 lambda 表达式但是并没有调用是不会生成内部类的。
(3)在生成内部类后,创建 ConstantCallSite
对象返回,如果
下面看下生成的内部类:
$ java -jar cfr-0.149.jar LambdaTest$$Lambda$3.class --decodelambdas false
/*
* Decompiled with CFR 0.149.
*
* Could not load the following classes:
* lll.LambdaTest
* lll.LambdaTest$TestFunction1
*/
package lll;
import java.lang.invoke.LambdaForm;
import lll.LambdaTest;
final class LambdaTest$$Lambda$3
implements LambdaTest.TestFunction1 {
private LambdaTest$$Lambda$3() {
}
@LambdaForm.Hidden
public String execute(Object object) {
return LambdaTest.lambda$main$5((String)((String)object));
}
}
可以看到,生成的内部类有 final 修饰,实现了函数式接口定义的方法,方法体就是一句直接调用 Javac 编译器为这个调用的 lambda 表达式生成的 private 方法。当 lambda 表达式执行时,实际上会调用这个内部类实例实现的函数式接口方法来执行。
注意这里的内部类没有任何属性,是因为这个对应的 lambda 表达式没有使用任何外部的局部变量或实例变量,如果有使用它们,内部类中就会生成对应的属性。
- 使用局部变量:
源代码:
public class FieldLambdaTest {
public static void main(String[] args) {
int i = 20;
ServiceFunction<Integer> serviceFunction = (num) -> {
System.out.println("ServiceFunction 执行");
System.out.println("输入:" + num);
// 访问外部的局部变量 i
return i + num;
};
System.out.println("结果:" + serviceFunction.service(10));
}
/**
* ServiceFunction,service 方法一个参数
*
* @param <T>
*/
@FunctionalInterface
private interface ServiceFunction<T> {
T service(T t);
}
}
反编译 class 文件:
$ java -jar cfr-0.149.jar FieldLambdaTest.class --decodelambdas false
/*
* Decompiled with CFR 0.149.
*/
package lll;
import java.lang.invoke.LambdaMetafactory;
public class FieldLambdaTest {
public static void main(String[] args) {
new FieldLambdaTest().calculate();
}
public void calculate() {
int i = 20;
ServiceFunction<Integer> serviceFunction = (ServiceFunction<Integer>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Ljava/lang/Object;
, lambda$calculate$0(int java.lang.Integer ), (Ljava/lang/Integer;)Ljava/lang/Integer;)((int)i);
System.out.println("\u7ed3\u679c\uff1a" + serviceFunction.service(10));
}
private static /* synthetic */ Integer lambda$calculate$0(int i, Integer num) {
System.out.println("ServiceFunction \u6267\u884c");
System.out.println("\u8f93\u5165\uff1a" + num);
return i + num;
}
@FunctionalInterface
private static interface ServiceFunction<T> {
public T service(T var1);
}
}
反编译生成的内部类:
$ java -jar cfr-0.149.jar FieldLambdaTest$$Lambda$1.class --decodelambdas false
/*
* Decompiled with CFR 0.149.
*
* Could not load the following classes:
* lll.FieldLambdaTest
* lll.FieldLambdaTest$ServiceFunction
*/
package lll;
import java.lang.invoke.LambdaForm;
import lll.FieldLambdaTest;
final class FieldLambdaTest$$Lambda$1
implements FieldLambdaTest.ServiceFunction {
private final int arg$1;
private FieldLambdaTest$$Lambda$1(int n) {
this.arg$1 = n;
}
private static FieldLambdaTest.ServiceFunction get$Lambda(int n) {
return new FieldLambdaTest$$Lambda$1(n);
}
@LambdaForm.Hidden
public Object service(Object object) {
return FieldLambdaTest.lambda$main$0((int)this.arg$1, (Integer)((Integer)object));
}
}
可以看到如果在 lambda 表达式中使用了外部的局部变量,那么
(1)Javac 编译器为该 lambda 表达式自动生成的方法会在对对应函数式接口方法的参数列表基础上再添加和所访问的外部局部变量类型和数量一致的参数,用于传递局部变量的值。
(2)运行时生成的内部类中会有和外部局部变量数量和类型相同的且用 private final 修饰的实例属性,这些属性会在构造器中用外部局部变量的当前值进行初始化,之后就不能再修改了,只能在这个类内部访问,没有提供任何额外的访问方法。
(3)这个内部类对应的 lambda 表达式执行时,实际上调用的是这个内部类实现的函数式接口方法,执行体中访问了外部局部变量,在这个内部类实现的方法体中调用 Javac 自动生成的方法时,传递的局部变量值就是内部类实例的这些属性值。
- 使用实例变量和外部局部变量
源代码:
public class FieldLambdaTest {
private int a = 30;
public static void main(String[] args) {
new FieldLambdaTest().calculate();
}
public void calculate() {
int i = 20;
ServiceFunction<Integer> serviceFunction = (num) -> {
System.out.println("ServiceFunction 执行");
System.out.println("输入:" + num);
return i + num + a;
};
System.out.println("结果:" + serviceFunction.service(10));
}
/**
* ServiceFunction,service 方法一个参数
*
* @param <T>
*/
@FunctionalInterface
private interface ServiceFunction<T> {
T service(T t);
}
}
反编译 class 文件:
$ java -jar cfr-0.149.jar FieldLambdaTest.class --decodelambdas false
/*
* Decompiled with CFR 0.149.
*/
package lll;
import java.lang.invoke.LambdaMetafactory;
public class FieldLambdaTest {
private int a = 30;
public static void main(String[] args) {
new FieldLambdaTest().calculate();
}
public void calculate() {
int i = 20;
ServiceFunction<Integer> serviceFunction = (ServiceFunction<Integer>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Ljava/lang/Object;
, lambda$calculate$0(int java.lang.Integer ), (Ljava/lang/Integer;)Ljava/lang/Integer;)((FieldLambdaTest)this, (int)i);
System.out.println("\u7ed3\u679c\uff1a" + serviceFunction.service(10));
}
private /* synthetic */ Integer lambda$calculate$0(int i, Integer num) {
System.out.println("ServiceFunction \u6267\u884c");
System.out.println("\u8f93\u5165\uff1a" + num);
return i + num + this.a;
}
@FunctionalInterface
private static interface ServiceFunction<T> {
public T service(T var1);
}
}
反编译生成的内部类:
$ java -jar cfr-0.149.jar FieldLambdaTest$$Lambda$1.class --decodelambdas false
/*
* Decompiled with CFR 0.149.
*
* Could not load the following classes:
* lll.FieldLambdaTest
* lll.FieldLambdaTest$ServiceFunction
*/
package lll;
import java.lang.invoke.LambdaForm;
import lll.FieldLambdaTest;
final class FieldLambdaTest$$Lambda$1
implements FieldLambdaTest.ServiceFunction {
private final FieldLambdaTest arg$1;
private final int arg$2;
private FieldLambdaTest$$Lambda$1(FieldLambdaTest fieldLambdaTest, int n) {
this.arg$1 = fieldLambdaTest;
this.arg$2 = n;
}
private static FieldLambdaTest.ServiceFunction get$Lambda(FieldLambdaTest fieldLambdaTest, int n) {
return new FieldLambdaTest$$Lambda$1(fieldLambdaTest, n);
}
@LambdaForm.Hidden
public Object service(Object object) {
return this.arg$1.lambda$calculate$0(this.arg$2, (Integer)object);
}
}
可以看到,如果在 lambda 执行体中访问了不管多少个实例属性,那么
(1)生成的内部类中会有一个类的实例属性,并且用 private final 修饰,这个实例属性会在构造器中初始化,之后就不能再修改了,提供了 private 的类方法,用于根据指定的类实例和属性值获取内部类实例。
(2)由于 lambda 表达式执行体访问了实例变量,所以 Javac 自动为它生成的方法是实例方法,在 lambda 表达式执行时,调用内部类实现的函数式接口方法,方法体中会把内部类实例的类实例属性作为调用对象来调用 Javac 自动生成的方法。
当 lambda 执行体中有访问类变量时,生成的内部类中不会针对这个类变量的访问添加额外属性,因为是最终是通过类来访问的。