一、导读
本文是对 Brian Goetz 的《Translation of Lambda Expressions》文章的翻译以及添加了一些个人的理解,本篇翻译并不包含原文中的全部章节,只是将一些重要的内容翻译分享给大家。本篇翻译将为后续推出的 ASM 处理 Lambda 表达式以及方法引用的文章提供理论依据。
二、关于本文
本文概括了将 Lambda 表达式和方法引用从 Java 源码到字节码的转换策略。Java 的 Lambda 表达式由 JSR 335 规范制定,然后由 Lambda Project 项目实现。语言特性概览可以在 State of the Lambda 中找到。
本文主要是介绍编译器遇到 Lambda 表达式的时候如何生成字节码以及 Java 语言在运行时如何参与评估 Lambda 表达式。本文大部分内容是介绍处理函数式接口的转换机制。
在 Java 中函数式接口是 Lambda 表达式的一个核心知识点。函数式接口是只有一个抽象方法的接口,例如 Runnable, Comparator 等。Lambda 表达式只支持函数式接口,例如下面的两个例子:
Runnable r = () -> { System.out.println("hello"); };Collections.sort(strings, (String a, String b) -> a.compareTo(b));三、依赖和符号
关于 Lambda 表达式的设计依赖多个在 JSR 292 中描述的特性,这些特性包括 invokedynamic、method handles 以及对 mehtod handles 和 method types 增强的 LDC 字节码形式。因为这些特性并不会表现在 Java 源码中,我们将会使用一些伪代码来表示这些特性:
对 method handle 常量简写:MH(引用类型 class-name.method-name)
对 method type 常量简写:MT(method-signature)
对 invokedynamic 简写:INDY((bootstrap, static args...)(dynamic args...))
四、转换策略
方法引用也会按照 Lambda 表达式一样的方式进行处理,但是大部分方法引用不需要被脱糖进到一个新方法中;我们可以简单的为一个为引用的方法加载一个常量方法句柄然后将其传给 metafactory。
五、Lambda 方法体脱糖
将 Lambda 表达式转换成字节码的第一步是将 Lambda 方法体脱糖到一个方法中。
对于脱糖有以下几个问题需要考虑:
将 Lambda 方法体脱糖到一个静态方法中还是一个实例方法中?
脱糖之后生成的方法应该放在哪一个类中?
脱糖之后生成的方法的可访问性应该是怎样的?
脱糖之后生成的方法的命名应该是怎样的?
如果需要一个适配器去桥接 Lambda 方法体签名和函数式接口方法签名(例如装箱、拆箱、基础类型的扩大和缩小转变、动态参数转换等),那么脱糖的方法是遵循 Lambda 方法体的签名还是函数式接口的签名,又或者是两者的结合呢?以及谁负责这个适配呢?
如果 Lambda 从外部作用域(enclosing scope)中获取参数,这些参数应该如何在脱糖的方法签名中表示呢?(例如它们可以被加到参数列表的前面或者后面,或者编译期可以将他们整合到一个 “frame”参数里面)
跟脱糖 Lambda 方法体时需要考虑的问题一样,我们也需要考虑方法引用是否需要一个适配器或者桥接方法。
编译器会推断 Lambda 表达式的方法签名,包括参数类型、返回值类型和异常信息,我们把这些称为 natural signature。Lambda 表达式也有一个目标类型,这个类型是一个函数式接口,我们将 Lambda 描述符称为删除了目标类型的方法签名。从 lambda factory 返回的实现了函数式接口和捕获了 Lambda 行为的值被称为 lambda object。
在同等条件下,私有方法优于非私有方法,静态方法优于实例方法,最好的结果是 Lambda 方法体被脱糖在它所在的类里面,脱糖后的签名应该匹配 Lambda 方法体的签名,需要的额外的参数应该被添加在参数列表的前面,而且完全不对方法引用进行脱糖。可是在某些情况下,我们不得不偏离这条基准策略。
class A {
public void foo() {
List<String> list = ...
list.forEach( s -> { System.out.println(s); } );
}
}class A {
public void foo() {
List<String> list = ...
list.forEach( [lambda for lambda$1 as Block] );
}
//这个就是脱糖产生的方法
static void lambda$1(String s) {
System.out.println(s);
}
}5.2 脱糖例子之 lambdas 捕获不变的值
class B {
public void foo() {
List<Person> list = ...
final int bottom = ..., top = ...;
list.removeIf( p -> (p.size >= bottom && p.size <= top) );
}
}class B {
public void foo() {
List<Person> list = ...
final int bottom = ..., top = ...;
list.removeIf( [ lambda for lambda$1 as Predicate capturing (bottom, top) ]);
}
//关注这个方法的签名
static boolean lambda$1(int bottom, int top, Person p) {
return (p.size >= bottom && p.size <= top;
}
}六、The Lambdas Metafactory
metaFactory(MethodHandles.Lookup caller, // provided by VM
String invokedName, // provided by VM
MethodType invokedType, // provided by VM
MethodHandle descriptor, // lambda descriptor
MethodHandle impl) // lambda body6.1 Lambda 捕获
class A {
public void foo() {
List<String> list = ...
list.forEach(indy((MH(metaFactory), MH(invokeVirtual Block.apply),
MH(invokeStatic A.lambda$1)( )));
}
private static void lambda$1(String s) {
System.out.println(s);
}
}class B { public void foo() {
List<Person> list = ...
final int bottom = ..., top = ...;
list.removeIf(indy((MH(metaFactory), MH(invokeVirtual Predicate.apply),
MH(invokeStatic B.lambda$1))( bottom, top ))));
}
private static boolean lambda$1(int bottom, int top, Person p) {
return (p.size >= bottom && p.size <= top;
}
}6.2 静态方法还是实例方法
list.filter(e -> e.getSize() < minSize )list.forEach(INDY((MH(metaFactory), MH(invokeVirtual Predicate.apply),
MH(invokeVirtual B.lambda$1))( this ))));
private boolean lambda$1(Element e) {
return e.getSize() < minSize;
}6.3 捕获方法引用
list.filter(String::isEmpty)list.filter(indy(MH(metaFactory), MH(invokeVirtual Predicate.apply),
MH(invokeVirtual String.isEmpty))()))
6.4 可变参数
interface IIS {
void foo(Integer a1, Integer a2, String a3);
}
class Foo {
static void m(Number a1, Object... rest) { ... }//第一个参数是 Number,后面的可变参数是 Object
}
class Bar {
void bar() {
SIS x = Foo::m;
}
}class Bar {
void bar() {
SIS x = indy((MH(metafactory), MH(invokeVirtual IIS.foo),
MH(invokeStatic m$bridge))( ))
}
static private void m$bridge(Integer a1, Integer a2, String a3) {//可以看到桥接方法参数跟函数式接口中的参数一致
Foo.m(a1, a2, a3);
}
}6.5 参数适配
T == U
T 是基础类型,U 是引用类型,且 T 可以被转换成 U 通过装箱
T 是引用类型,U 是基础类型,且 R 可以转换成 U 通过拆箱
T 和 U 都是基础类型,T 可以扩展转换成 U(注:例如 int 可以转换成 long)
T 和 U 都是引用类型,且 T 可以强制转换成 U
七、总结
参考文献:
https://twitter.com/BrianGoetz
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html
https://jcp.org/en/jsr/detail?id=335
http://openjdk.java.net/projects/lambda/
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html
https://jcp.org/en/jsr/detail?id=292