以下代码在 Java 1.8 VM 中运行良好,但在 Java 11 VM 中执行时会产生LambdaConversionException
。区别在哪里,为什么它表现得这样?
法典:
public void addSomeListener(Component comp){
if(comp instanceof HasValue) {
((HasValue<?,?>) comp).addValueChangeListener(evt -> {
//do sth with evt
});
}
}
HasValue Javadoc
异常(仅限 V11):
Caused by: java.lang.invoke.LambdaConversionException: Type mismatch
for instantiated parameter 0: class java.lang.Object is not a subtype
of interface com.vaadin.flow.component.HasValue$ValueChangeEvent
at java.base/java.lang.invoke.AbstractValidatingLambdaMetafactory.checkDescriptor(AbstractValidatingLambdaMetafactory.java:308)
at java.base/java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:294)
at java.base/java.lang.invoke.LambdaMetafactory.altMetafactory(LambdaMetafactory.java:503)
at java.base/java.lang.invoke.BootstrapMethodInvoker.invoke(BootstrapMethodInvoker.java:138)
... 73 more
解决方法:
ValueChangeListener<ValueChangeEvent<?>> listener = evt -> {
// do sth with evt
};
((HasValue<?,?>) comp).addValueChangeListener(listener);
系统:操作系统:视窗 10
IDE:
Eclipse 2018-12 (4.10.0)
Java(编译):ecj
Java(网络服务器):JDK 11.0.2
网络服务器:野蝇15
TL;DR Eclipse 编译器为 lambda 实例生成一个方法签名,根据规范,该方法签名无效。由于 JDK 9 中添加了额外的类型检查代码以更好地强制实施规范,因此在 Java 11 上运行时,不正确的签名现在会导致异常。
使用 Eclipse 2019-03 以及以下代码进行了验证:
public class Main {
public static void main(String[] args) {
getHasValue().addValueChangeListener(evt -> {});
}
public static HasValue<?, ?> getHasValue() {
return null;
}
}
interface HasValue<E extends HasValue.ValueChangeEvent<V>,V> {
public static interface ValueChangeEvent<V> {}
public static interface ValueChangeListener<E extends HasValue.ValueChangeEvent<?>> {
void valueChanged(E event);
}
void addValueChangeListener(HasValue.ValueChangeListener<? super E> listener);
}
即使使用null
作为接收器,代码在引导时也会失败,并出现相同的错误。
使用javap -v Main
我们可以看到问题出在哪里。我在BoostrapMethods表中看到了这个:
BootstrapMethods:
0: #48 REF_invokeStatic 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;
Method arguments:
#50 (Lmain/HasValue$ValueChangeEvent;)V
#53 REF_invokeStatic main/Main.lambda$0:(Ljava/lang/Object;)V
#54 (Ljava/lang/Object;)V
请注意,最后一个参数(常量 #54)是(Ljava/lang/Object;)V
,而javac
生成(Lmain/HasValue$ValueChangeEvent;)V
,即 Eclipse 想要用于 lambda 的方法签名与javac
想要使用的方法签名不同。
如果想要的方法签名是目标方法的擦除(似乎是这种情况),那么正确的方法签名确实是(Lmain/HasValue$ValueChangeEvent;)V
,因为这是目标方法的擦除,即:
void valueChanged(E event);
E
E extends HasValue.ValueChangeEvent<?>
的地方,因此将被删除以HasValue.ValueChangeEvent
。
问题似乎出在 ECJ 上,并且似乎已被 JDK-8173587(修订版)浮出水面(不幸的是,这似乎是一张私人票证),它添加了额外的类型检查以验证 SAM 方法类型是否实际上与实例化方法类型兼容。根据LambdaMetafactory::metafactory
的文档,实例化的方法类型必须相同,或者是SAM方法类型的专用化:
实例化方法类型 - 应在调用时动态强制执行的签名和返回类型。这可能与 samMethodType 相同,也可能是它的专用化。
ECJ 生成的方法类型显然不是,因此这最终会引发异常。(不过,公平地说,在这种情况下,我没有看到任何地方定义什么构成"专业化")。我已经在Eclipse bugzilla上报告了这个:https://bugs.eclipse.org/bugs/show_bug.cgi?id=546161
我猜这个更改是在JDK 9的某个地方进行的,因为当时源代码已经是模块化的,而且修订日期相当早(2017年2月)。
由于javac
生成正确的方法签名,因此您可以暂时切换到该方法作为解决方法。