簡體   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