前言
无。
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
| 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;
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