我遇到了使用方法引用而不是lambda发生的问题。该代码如下:
(Comparator<ObjectNode> & Serializable) SOME_COMPARATOR::compare
或者,用lambda
(Comparator<ObjectNode> & Serializable) (a, b) -> SOME_COMPARATOR.compare(a, b)
从语义上讲,它是严格相同的,但实际上与第一种情况不同,我在一个Java序列化类中遇到了一个异常。我的问题不是关于此异常的问题,因为实际的代码正在更复杂的上下文中运行,事实证明该序列化具有奇怪的行为,因此如果我提供更多详细信息,这将使回答变得非常困难。
我想了解的是这两种创建lambda表达式的方式之间的区别。
为了对此进行研究,我们从以下类开始:
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: ireturn
BootstrapMethods:
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的代码。在Method arguments
下面的每个自举方法列出的是作为可变参数的参数(即,传递的值args
的)LambdaMetaFactory#altMetaFactory。
在这两种情况下,这里bridgeCount
都是0,所以就没有6,否则将是bridges
-要实现的其他方法签名的可变长度列表(假定bridgeCount
为0,我不完全确定为什么设置了FLAG_BRIDGES)。
将以上内容与我们的论据相匹配,我们得到:
(Ljava/lang/Object;Ljava/lang/Object;)I
(即Comparator#compare的返回类型)。(LInteger;LInteger;)I
请注意,这些不会被擦除,因为这是lambda规范的一部分)。我们可以看到为两个lambda都设置了FLAG_SERIALIZABLE,所以不是那样。
方法参考lambda的实现方法为Comparator.compare:(LObject;LObject;)I
,但显式lambda
的实现方法为Generic.lambda$explicit$df5d232f$1:(LInteger;LInteger;)I
。查看反汇编,我们可以看到前者本质上是后者的内联版本。唯一的其他显着差异是方法参数类型(如前所述,这是由于通用类型擦除)。
如果lambda表达式的目标类型和捕获的参数可序列化,则可以对其进行序列化。
Lambda表达式(Java™教程)
其中重要的部分是“捕获的参数”。回头看一下反汇编的字节码,方法引用的invokedynamic指令肯定看起来像是在捕获Comparator(#0:compare:(LComparator;)LComparator;
与显式lambda相反#1:compare:()LComparator;
)。
ObjectOutputStream
包含一个extendedDebugInfo
字段,我们可以使用-Dsun.io.serialization.extendedDebugInfo=true
VM参数设置该字段:
$ 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)
/* removed */
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
值,因此失败。
我很犹豫地将其称为编译器错误(我希望缺乏间接性可以起到优化作用),尽管这很奇怪。解决方法是微不足道的,但是很丑。添加COMPARATOR
at声明的显式强制转换:
public static final Comparator<Integer> COMPARATOR = (Serializable & Comparator<Integer>) (a, b) -> a > b ? 1 : -1;
这使所有内容都能在Java
1.8.0_45上正确执行。还值得注意的是,eclipse编译器也会在方法参考案例中产生该间接层,因此本文中的原始代码不需要修改即可正确执行。
我在使用方法引用时遇到了一个问题,但在使用lambdas时没有遇到。代码如下: 或者,与lambda, 在语义上,它是严格相同的,但在实践中它是不同的,因为在第一种情况下,我在一个Java序列化类中得到了一个异常。我的问题不是关于这个异常,因为实际的代码运行在一个更复杂的上下文中,这个上下文已被证明具有奇怪的序列化行为,所以如果我提供更多细节,那么回答这个问题就太困难了。 我想了解的是这两种创建l
问题内容: 斯威夫特有: 强引用 参考文献薄弱 无人参考 无主引用与弱引用有何不同? 什么时候可以使用无主引用安全? 无主引用是否像C / C ++中的悬空指针一样具有安全风险? 问题答案: 双方并引用不创建一个被引用的对象上保持(又名它们不会取消分配引用的对象增加,为了保留计数,以防止电弧)。 但是为什么要两个关键词呢?这种区别与类型内置在Swift语言中这一事实有关。长话短说:可选类型提供了内
问题内容: 在此示例中: 无法编译为: 而被编译器接受。 这个答案说明唯一的区别是,与不同,它允许您稍后引用类型,似乎并非如此。 是什么区别,并在这种情况下,为什么不第一编译? 问题答案: 通过使用以下签名定义方法: 并像这样调用它: 在jls§8.1.2中,我们发现(有趣的部分被我加粗了): 通用类声明定义了一组参数化类型(第4.5节), 每种可能通过类型arguments调用类型参数节的类型
问题内容: 将一个使用在另一个上是否有好处?在Python 2中,它们似乎都返回相同的结果: 问题答案: 在将返回2.5并且将返回2。前者是浮点除法,后者是地板除法,有时也称为整数除法。 在或更高版本的2.x行中,除非执行,否则整数没有区别,这会使采取3.0的行为。 不管将来的进口是什么,都会归还,2.0因为这是操作的地板分割结果。
和有什么区别?他们彼此有关系吗?或者它们只是并发实现? 是否有人与他们一起工作,并能给出/解释两者的利弊? 使用我指的是ng-bootstrap.github和 与我的意思是valor-software-ngx-bootstrap。 两者都与Angular 4有关(不是AngularJS!)和引导4。 请注意,这不是一个重复的问题ngx-bootstrap和ng2 bootstrap之间的区别?。
问题内容: 我对python级别的函数和常规函数(用定义)之间的差异感到好奇。(我知道对程序员有什么区别,以及何时使用每个程序员。) 如我们所见-python 知道 这是一个函数,并且是一个常规函数。这是为什么?它们 与python有 什么区别? 问题答案: 它们是同一类型,因此它们的处理方式相同: Python还知道将其定义为lambda函数,并将其设置为函数名称: 换句话说,它影响了该函数将获