繁体   English   中英

Java Casting:Java 11抛出LambdaConversionException而1.8抛出LambdaConversionException

[英]Java Casting: Java 11 throws LambdaConversionException while 1.8 does not

以下代码在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);

系统:
操作系统:Windows 10
IDE:Eclipse 2018-12(4.10.0)
Java(编译):ecj
Java(Webserver):JDK 11.0.2
网络服务器:Wildfly 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);

其中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生成了正确的方法签名,因此您可以暂时将其切换为解决方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM