Java Lambda

前言

无。


Lambda

闭包函数,取代匿名内部类,用于实现判断、过滤、排序等功能:

1
2
3
4
5
6
7
8
9
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的东西,指向一个函数的具体实现,同样可以通过它进行反射函数调用:

1
2
3
4
5
6
7
8
9
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());
}

类字节码分析

整个测试用类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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函数第一部分的字节码:

1
2
3
4
5
6
7
8
9
 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就是将这些杂乱数据合并的工具:

1
<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函数,该函数有多个输入参数,这些参数的意义可以从定义和注释中一窥一二:

1
2
3
4
5
6
public static CallSite metafactory(MethodHandles.Lookup caller,
String interfaceMethodName,
MethodType factoryType,
MethodType interfaceMethodType,
MethodHandle implementation,
MethodType dynamicMethodType)

从注释里可以看到这些函数的具体含义,caller看起来是虚拟机自动处理的参数,其他参数可以通过调试的方式看到具体数据,基本都是前面常量池中看到的数据:

该函数返回一个CallSite对象,继续调试,进入InnerClassLambdaMetafactory类的buildCallSite函数。该函数首先调用spinInnerClass函数,在generateInnerClass函数中创造了一个匿名内部类,最后将接口的具体实现写入到内存中:

1
2
3
4
// Forward the SAM method
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, interfaceMethodName,
interfaceMethodType.toMethodDescriptorString(), null, null);
new ForwardingMethodGenerator(mv).generate(interfaceMethodType);

根据参考文章,可以使用运行配置将这些内存匿名类打印出来:

1
-Djdk.internal.lambda.dumpProxyClasses=<path>

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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里:

1
2
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方法使用的是一个匿名函数,该函数的字节码如下:

1
2
3
4
5
6
7
8
 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表达式生成的一个匿名函数,其他操作都差不多。


参考

Java Lambda表达式详解

同事:Lambda都不懂,你还写什么Java


Java Lambda
http://yoursite.com/2024/05/05/Java-Lambda/
作者
Aluvion
发布于
2024年5月5日
许可协议