Java类型转换:Java 11在Lambda表达式转换时会抛出LambdaConversionException异常,而1.8则不会。

63

以下代码在Java 1.8虚拟机中完美运行,但在Java 11虚拟机中执行时会产生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);

系统:
操作系统:Windows 10
IDE:Eclipse 2018-12 (4.10.0)
Java(编译):ecj
Java(Web服务器):JDK 11.0.2
Web服务器:Wildfly 15


2
有趣但可悲的是,(HasValue<?,?>) comp 这个东西...虽然同意这样的验证在JDK-8代码中不存在。 - Naman
嗯...我使用官方的Vaadin启动项目建立了一个小的Hello World示例。我添加了你的方法,并添加了一个TextField,将其传递给你的addSomeListener方法。它可以正常工作,没有出现错误,并且当更改TextField的值时,添加的监听器会按预期执行。(使用jetty和Java 11进行测试:jetty-9.4.11.v20180605; built: 2018-06-05T18:24:03.829Z; git: d5fc0523cfa96bfebfbda19606cad384d772f04c; jvm 11.0.1+13 - codinghaus
你使用的编译器是哪个?这看起来像是生成的字节码出现了问题。 - Jorn Vernee
8
这是一个重要的信息,因为Eclipse有自己的编译器,所以由编译器引起的问题(这看起来非常像编译器问题)不必适用于javac - Holger
2
@JornVernee 更改语言级别有时会导致编译器的行为变化。 - Holger
显示剩余4条评论
1个回答

56

简而言之,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可以看出问题所在。我在BootstrapMethods表中看到了这个问题:

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);

其中EE extends HasValue.ValueChangeEvent<?>,所以它将被擦除为HasValue.ValueChangeEvent

问题似乎出在ECJ上,并且似乎已被JDK-8173587修订版)揭示了这一问题。(遗憾的是,这似乎是一个私人工单。)该工单增加了额外的类型检查,以验证SAM方法类型实际上与实例化方法类型兼容。根据LambdaMetafactory::metafactory的文档,实例化方法类型必须与SAM方法类型相同或是其特定形式:

instantiatedMethodType -应在调用时动态强制执行的签名和返回类型。这可以与samMethodType相同,也可以是其特定形式。

而ECJ生成的方法类型显然不是这样的,因此最终会抛出异常。(尽管公平地说,我没有看到在这种情况下什么构成了“特定形式”)。我已经在Eclipse bugzilla上报告了这个问题:https://bugs.eclipse.org/bugs/show_bug.cgi?id=546161

我猜测这个变化是在JDK 9的某个地方进行的,因为那时源代码已经模块化了,并且修订版的日期相当早(2017年2月)。

由于javac生成了正确的方法签名,因此您可以暂时将其切换为解决方法。


10
作为一个习惯于查看字节码的人,我倾向于过于复杂化这些与字节码相关的答案。很难在详细解释和简洁回答之间找到平衡点。如果有任何不清楚的地方,请随时要求澄清。 - Jorn Vernee
4
这并不是过于复杂的答案,而且很容易阅读和理解,没有比这更好的答案了。我很高兴看到它,谢谢你花时间写下它。 - Eugene
请问您能否添加一个链接到相应的Eclipse bug吗? - howlger
@JornVernee 一点也不。我认为你非常好地解释了与字节码相关的答案。 - Michael Berry
@JornVernee 如果还不存在,请向 Eclipse 报告此问题,并将错误链接添加到您的答案中。 - howlger
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接