IT TIP

Java Casting : Java 11에서는 LambdaConversionException이 발생하지만 1.8에서는 발생하지 않습니다.

itqueen 2021. 1. 8. 22:39
반응형

Java Casting : Java 11에서는 LambdaConversionException이 발생하지만 1.8에서는 발생하지 않습니다.


다음 코드는 Java 1.8 VM에서 완벽하게 작동하지만 LambdaConversionExceptionJava 11 VM에서 실행될 때 생성 됩니다. 차이점은 어디에 있으며 왜 이렇게 작동합니까?


암호:

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

시스템 :
OS : Windows 10
IDE : Eclipse 2018-12 (4.10.0)
Java (컴파일) : ecj
Java (웹 서버) : JDK 11.0.2
웹 서버 : Wildfly 15


TL; DR Eclipse 컴파일러는 사양에 따라 잘못된 람다 인스턴스에 대한 메서드 서명을 생성합니다. 사양을 더 잘 적용하기 위해 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);
}

Even when using null as the receiver, the code fails when bootstrapping with the same error.

Using javap -v Main we can see where the problem lies. I'm seeing this in the BoostrapMethods table:

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

Note that the last argument (constant #54) is (Ljava/lang/Object;)V, while javac generates (Lmain/HasValue$ValueChangeEvent;)V. i.e. the method signature that Eclipse wants to use for the lambda is different from what javac wants to use.

If the wanted method signature is the erasure of the target method (which seems to be the case), then the correct method signature is indeed (Lmain/HasValue$ValueChangeEvent;)V since that is the erasure of the target method, which is:

void valueChanged(E event);

Where E is E extends HasValue.ValueChangeEvent<?>, so that would be erased to HasValue.ValueChangeEvent.

The problem seems to be with ECJ, and seems to have been brought to the surface by JDK-8173587 (revision) (Unfortunately this seems to be a private ticket.) which adds extra type checks to verify that the SAM method type is actually compatible with the instantiate method type. According to the documentation of LambdaMetafactory::metafactory the instantiated method type must be the same, or a specialization of the SAM method type:

instantiatedMethodType - The signature and return type that should be enforced dynamically at invocation time. This may be the same as samMethodType, or may be a specialization of it.

which the method type generated by ECJ is evidently not, so this ends up throwing an exception. (though, to be fair, I don't see defined anywhere what constitutes a "specialization" in this case). I've reported this on the Eclipse bugzilla here: https://bugs.eclipse.org/bugs/show_bug.cgi?id=546161

I'm guessing this change was made somewhere in JDK 9, since source code was already modular at that point, and the date of the revision is fairly early (February 2017).

Since javac generates the correct method signature, you could switch to that for the time being as a workaround.

ReferenceURL : https://stackoverflow.com/questions/55532055/java-casting-java-11-throws-lambdaconversionexception-while-1-8-does-not

반응형