前言
无。
Lambda
闭包函数,取代匿名内部类,用于实现判断、过滤、排序等功能:
Consumer<String> consumer = System.out::println;
consumer.accept("Hello World!");
List<String> list = new ArrayList<>() {{
add("1");
add("2");
add("3");
}};
list.forEach(System.out::print);
此外,还有Predicate、Function、Supplier等多种功能接口。
MethodHandle
Java Lambda的实现与MethodHandle有关,这是一种类似反射中Method的东西,指向一个函数的具体实现,同样可以通过它进行反射函数调用:
Main main = new Main();
MethodType methodType = MethodType.methodType(void.class);
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
try {
MethodHandle methodHandle = lookup.findVirtual(main.getClass(), "test", methodType);
methodHandle.invoke(main);
}catch (Throwable e) {
System.out.println(e.getMessage());
}
类字节码分析
整个测试用类如下:
public class Main {
public static void main(String[] args) {
Main main = new Main();
MethodType methodType = MethodType.methodType(void.class);
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
try {
MethodHandle methodHandle = lookup.findVirtual(main.getClass(), "test", methodType);
methodHandle.invoke(main);
}catch (Throwable e) {
System.out.println(e.getMessage());
}
}
public void test() {
Consumer<String> consumer = System.out::println;
consumer.accept("Hello World!");
List<Integer> list = new ArrayList<>() {{
add(1);
add(2);
add(3);
}};
list.stream().filter(i -> i > 1).forEach(System.out::print);
}
}
先看比较简单的Consumer接口调用,观察test函数第一部分的字节码:
0 getstatic #48 <java/lang/System.out : Ljava/io/PrintStream;>
3 dup
4 invokestatic #64 <java/util/Objects.requireNonNull : (Ljava/lang/Object;)Ljava/lang/Object;>
7 pop
8 invokedynamic #70 <accept, BootstrapMethods #0>
13 astore_1
14 aload_1
15 ldc #74 <Hello World!>
17 invokeinterface #76 <java/util/function/Consumer.accept : (Ljava/lang/Object;)V> count 2
首先通过getstatic指令拿到了System.out.PrintStream这个println静态函数所属的类,然后dup复制再用invokestatic指令保证其不为空,接着是是与lambda强相关的invokedynamic指令。该指令指向的常量池#70包括一段常量池信息#71和一个Bootstrap方法#0:
常量池信息#71是一段NameAndType类型的信息,包含方法名和方法描述符,看起来分别是待调用的接口函数名accept,接口具体实现即println有关的类PrintStream,以及接口名Consumer,具体用处尚不清楚。
Bootstrap方法#0包含一个MethodHandle相关的常量池信息#144,和三个输入参数:
MethodHandle如上文所说,指向的是一个函数的具体实现,这里的两个MethodHandle中PrintStream.println是Consumer接口的具体实现,那么常量池信息#144指向的MethodHandle就是将这些杂乱数据合并的工具:
<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;>
该MethodHandle实际上是LambdaMetafactory类的metafactory函数,该函数有多个输入参数,这些参数的意义可以从定义和注释中一窥一二:
public static CallSite metafactory(MethodHandles.Lookup caller,
String interfaceMethodName,
MethodType factoryType,
MethodType interfaceMethodType,
MethodHandle implementation,
MethodType dynamicMethodType)
从注释里可以看到这些函数的具体含义,caller看起来是虚拟机自动处理的参数,其他参数可以通过调试的方式看到具体数据,基本都是前面常量池中看到的数据:
该函数返回一个CallSite对象,继续调试,进入InnerClassLambdaMetafactory类的buildCallSite函数。该函数首先调用spinInnerClass函数,在generateInnerClass函数中创造了一个匿名内部类,最后将接口的具体实现写入到内存中:
// Forward the SAM method
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, interfaceMethodName,
interfaceMethodType.toMethodDescriptorString(), null, null);
new ForwardingMethodGenerator(mv).generate(interfaceMethodType);
根据参考文章,可以使用运行配置将这些内存匿名类打印出来:
-Djdk.internal.lambda.dumpProxyClasses=<path>
结果如下:
package org.example;
import java.io.PrintStream;
import java.util.function.Consumer;
// $FF: synthetic class
final class Main$$Lambda$14 implements Consumer {
private final PrintStream arg$1;
private Main$$Lambda$14(PrintStream var1) {
this.arg$1 = var1;
}
public void accept(Object var1) {
this.arg$1.println((String)var1);
}
}
实际上就是一个继承并实现了Consumer接口的匿名类,我们在代码中写的lambda表达式都会被写入到accept函数中,后面字节码在接口调用时就能触发。
最后实例化为一个ConstantCallSite对象返回,并将一个MethodHandle绑定到该CallSite里:
MethodHandle mh = caller.findConstructor(innerClass, constructorType);
return new ConstantCallSite(mh.asType(factoryType));
在经过一系列底层代码后,会调用MethodHandle的invokeBasic方法,得到对应的Consumer对象,并其将作为指令结果压入栈中。
在后续的指令中,会通过invokeinterface指令调用该对象的accept函数,从而调用到我们写的lambda表达式。
然后是第二部分,在filter中写的稍微复杂一点的lambda表达式,其主要的差别在于Bootstrap方法:
与前一个Bootstrap方法不同,前一个Bootstrap方法的MethodHandle直接使用的是PrintStream.println,而这一个Bootstrap方法使用的是一个匿名函数,该函数的字节码如下:
0 aload_0
1 invokevirtual #105 <java/lang/Integer.intValue : ()I>
4 iconst_1
5 if_icmple 12 (+7)
8 iconst_1
9 goto 13 (+4)
12 iconst_0
13 ireturn
实际上就是用我们写的lambda表达式生成的一个匿名函数,其他操作都差不多。