入门
为了对此进行研究,我们从以下类开始:
import java.io.Serializable;import java.util.Comparator;public final class Generic { // Bad implementation, only used as an example. public static final Comparator<Integer> COMPARATOR = (a, b) -> (a > b) ? 1 : -1; public static Comparator<Integer> reference() { return (Comparator<Integer> & Serializable) COMPARATOR::compare; } public static Comparator<Integer> explicit() { return (Comparator<Integer> & Serializable) (a, b) -> COMPARATOR.compare(a, b); }}编译后,我们可以使用以下命令对其进行反汇编:
javap -c -p -s -v Generic.class
除去不相关的部分(以及其他一些杂物,例如完全限定的类型和的初始化
COMPARATOR),我们剩下的就是
public static final Comparator<Integer> COMPARATOR; public static Comparator<Integer> reference(); 0: getstatic #2 // Field COMPARATOR:LComparator; 3: dup 4: invokevirtual #3 // Method Object.getClass:()LClass; 7: pop 8: invokedynamic #4, 0 // InvokeDynamic #0:compare:(LComparator;)LComparator; 13: checkcast #5 // class Serializable 16: checkcast #6 // class Comparator 19: areturn public static Comparator<Integer> explicit(); 0: invokedynamic #7, 0 // InvokeDynamic #1:compare:()LComparator; 5: checkcast #5 // class Serializable 8: checkcast #6 // class Comparator 11: areturn private static int lambda$explicit$d34e1a25$1(Integer, Integer); 0: getstatic #2 // Field COMPARATOR:LComparator; 3: aload_0 4: aload_1 5: invokeinterface #44, 3 // InterfaceMethod Comparator.compare:(LObject;LObject;)I 10: ireturnBootstrapMethods: 0: #61 invokestatic invoke/Lambdametafactory.altmetafactory:(Linvoke/MethodHandles$Lookup;LString;Linvoke/MethodType;[LObject;)Linvoke/CallSite; Method arguments: #62 (LObject;LObject;)I #63 invokeinterface Comparator.compare:(LObject;LObject;)I #64 (LInteger;LInteger;)I #65 5 #66 0 1: #61 invokestatic invoke/Lambdametafactory.altmetafactory:(Linvoke/MethodHandles$Lookup;LString;Linvoke/MethodType;[LObject;)Linvoke/CallSite; Method arguments: #62 (LObject;LObject;)I #70 invokestatic Generic.lambda$explicit$df5d232f$1:(LInteger;LInteger;)I #64 (LInteger;LInteger;)I #65 5 #66 0
立即我们看到该
reference()方法的字节码与的字节码不同
explicit()。但是,显着的差异实际上并不相关,但是引导方法很有趣。
invokedynamic调用站点通过 bootstrap方法 链接到一个方法,该 方法 是由编译器为动态类型语言指定的一种 方法
,该方法由JVM调用一次以链接该站点。
(对非Java语言的Java虚拟机支持,重点是它们的)
这是负责创建lambda使用的CallSite的代码。在
Methodarguments下面的每个自举方法列出的是作为可变参数的参数(即,传递的值
args的)LambdametaFactory#altmetaFactory。
方法参数的格式
- samMethodType-函数对象要实现的方法的签名和返回类型。
- implMethod-一个直接的方法句柄,描述在调用时应调用的实现方法(对参数类型,返回类型进行适当的调整,并在调用参数之前添加捕获的参数)。
- InstantiatedMethodType-应该在调用时动态强制执行的签名和返回类型。这可能与samMethodType相同,也可能是它的特殊化。
- 标志表示其他选项;这是所需标志的按位或。定义的标志是FLAG_BRIDGES,FLAG_MARKERS和FLAG_SERIALIZABLE。
- bridgeCount是功能对象应实现的其他方法签名的数量,并且仅当设置了FLAG_BRIDGES标志时才存在。
在这两种情况下,这里
bridgeCount都是0,所以就没有6,否则将是
bridges-要实现的其他方法签名的可变长度列表(假定
bridgeCount为0,我不完全确定为什么设置了FLAG_BRIDGES)。
将以上内容与我们的论据相匹配,我们得到:
- 由于通用类型擦除,因此函数签名和返回类型
(Ljava/lang/Object;Ljava/lang/Object;)I
(即Comparator#compare的返回类型)。 - 调用此lambda时被调用的方法(不同)。
- lambda的签名和返回类型,将在调用lambda时进行检查:(
(LInteger;LInteger;)I
请注意,这些不会被擦除,因为这是lambda规范的一部分)。 - 这些标志,在两种情况下都是FLAG_BRIDGES和FLAG_SERIALIZABLE的组成(即5)。
- 桥接方法签名的数量,0。
我们可以看到为两个lambda都设置了FLAG_SERIALIZABLE,所以不是那样。
实施方法
方法参考lambda的实现方法为
Comparator.compare:(LObject;LObject;)I,但显式lambda
的实现方法为
Generic.lambda$explicit$df5d232f$1:(LInteger;LInteger;)I。查看反汇编,我们可以看到前者本质上是后者的内联版本。唯一的其他显着差异是方法参数类型(如前所述,这是由于通用类型擦除)。
Lambda何时可序列化?
如果lambda表达式的目标类型和捕获的参数可序列化,则可以对其进行序列化。
Lambda表达式(Java™教程)
其中重要的部分是“捕获的参数”。回头看一下反汇编的字节码,方法引用的invokedynamic指令肯定看起来像是在捕获Comparator(
#0:compare:(LComparator;)LComparator;与显式lambda相反
#1:compare:()LComparator;)。
确认捕获是问题
ObjectOutputStream包含一个
extendedDebugInfo字段,我们可以使用
-Dsun.io.serialization.extendedDebugInfo=trueVM参数设置该字段:
$ java -Dsun.io.serialization.extendedDebugInfo = true通用
当我们尝试再次序列化lambda时,这给出了非常令人满意的结果
Exception in thread "main" java.io.NotSerializableException: Generic$$Lambda$1/321001045 - element of array (index: 0) - array (class "[LObject;", size: 1) - field (class "invoke.SerializedLambda", name: "capturedArgs", type: "class [LObject;") // <--- !! - root object (class "invoke.SerializedLambda", SerializedLambda[capturingClass=class Generic, functionalInterfaceMethod=Comparator.compare:(LObject;LObject;)I, implementation=invokeInterface Comparator.compare:(LObject;LObject;)I, instantiatedMethodType=(LInteger;LInteger;)I, numCaptured=1]) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1182) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at Generic.main(Generic.java:27)
实际发生了什么
从上面可以看出,显式lambda 不能 捕获任何内容,而方法引用lambda可以捕获任何内容。再次查看字节码可以清楚地看出这一点:
public static Comparator<Integer> explicit(); 0: invokedynamic #7, 0 // InvokeDynamic #1:compare:()LComparator; 5: checkcast #5 // class java/io/Serializable 8: checkcast #6 // class Comparator 11: areturn
如上所示,它具有以下实现方法:
private static int lambda$explicit$d34e1a25$1(java.lang.Integer, java.lang.Integer); 0: getstatic #2 // Field COMPARATOR:Ljava/util/Comparator; 3: aload_0 4: aload_1 5: invokeinterface #44, 3 // InterfaceMethod java/util/Comparator.compare:(Ljava/lang/Object;Ljava/lang/Object;)I 10: ireturn
显式lambda实际上正在调用
lambda$explicit$d34e1a25$1,而后者又调用
COMPARATOR#compare。间接层意味着它不会捕获任何不是的东西
Serializable(确切地说是任何东西),因此可以安全地进行序列化。方法引用表达式
直接 使用
COMPARATOR(然后将其值传递给bootstrap方法):
public static Comparator<Integer> reference(); 0: getstatic #2 // Field COMPARATOR:LComparator; 3: dup 4: invokevirtual #3 // Method Object.getClass:()LClass; 7: pop 8: invokedynamic #4, 0 // InvokeDynamic #0:compare:(LComparator;)LComparator; 13: checkcast #5 // class java/io/Serializable 16: checkcast #6 // class Comparator 19: areturn
缺少间接意味着
COMPARATOR必须与lambda一起序列化。由于
COMPARATOR未引用
Serializable值,因此失败。
解决方法
我很犹豫地将其称为编译器错误(我希望缺乏间接性可以起到优化作用),尽管这很奇怪。解决方法是微不足道的,但是很丑。添加
COMPARATORat声明的显式强制转换:
public static final Comparator<Integer> COMPARATOR = (Serializable & Comparator<Integer>) (a, b) -> a > b ? 1 : -1;
这使所有内容都能在Java
1.8.0_45上正确执行。还值得注意的是,eclipse编译器也会在方法参考案例中产生该间接层,因此本文中的原始代码不需要修改即可正确执行。



