java eclipse java-8 java-11 vaadin-flow

Java Casting: Java 11 lanza LambdaConversionException mientras que 1.8 no lo hace



eclipse java-8 (1)

TL; DR El compilador de Eclipse genera una firma de método para la instancia lambda que no es válida de acuerdo con la especificación. Debido al código de verificación de tipo adicional agregado en JDK 9 para aplicar mejor la especificación, la firma incorrecta ahora está causando una excepción cuando se ejecuta en Java 11.

Verificado con Eclipse 2019-03 también con este código:

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

Incluso cuando se usa null como receptor, el código falla al arrancar con el mismo error.

Usando javap -v Main podemos ver dónde está el problema. Estoy viendo esto en la tabla 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

Tenga en cuenta que el último argumento (constante # 54) es (Ljava/lang/Object;)V , mientras que javac genera (Lmain/HasValue$ValueChangeEvent;)V es decir, la firma del método que Eclipse quiere usar para la lambda es diferente de lo que javac quiere usar.

Si la firma del método deseado es el borrado del método de destino (que parece ser el caso), entonces la firma del método correcto es de hecho (Lmain/HasValue$ValueChangeEvent;)V ya que ese es el borrado del método de destino, que es:

void valueChanged(E event);

Donde E es E extends HasValue.ValueChangeEvent<?> , Para que se borre a HasValue.ValueChangeEvent .

El problema parece ser con ECJ, y parece haber sido traído a la superficie por JDK-8173587 ( revision ) (Desafortunadamente, esto parece ser un boleto privado), que agrega controles de tipo adicionales para verificar que el tipo de método SAM sea realmente compatible con el tipo de método de instanciación. De acuerdo con la documentación de LambdaMetafactory::metafactory el tipo de método instanciado debe ser el mismo, o una especialización del tipo de método SAM:

instantiatedMethodType: el tipo de firma y devolución que se debe aplicar dinámicamente en el momento de la invocación. Esto puede ser lo mismo que samMethodType, o puede ser una especialización de él.

el tipo de método generado por ECJ es evidentemente no, por lo que esto termina lanzando una excepción. (aunque, para ser justos, no veo definido en ninguna parte lo que constituye una "especialización" en este caso). He reportado esto en el bugzilla de Eclipse aquí: https://bugs.eclipse.org/bugs/show_bug.cgi?id=546161

Supongo que este cambio se realizó en algún lugar del JDK 9, ya que el código fuente ya era modular en ese momento, y la fecha de la revisión es bastante temprana (febrero de 2017).

Ya que javac genera la firma del método correcto, podría cambiar a eso por el momento como solución alternativa.

El siguiente código funciona perfectamente en una máquina virtual Java 1.8, pero produce una excepción LambdaConversionException cuando se ejecuta en una máquina virtual Java 11. ¿Dónde está la diferencia y por qué se comporta así?

Código:

public void addSomeListener(Component comp){ if(comp instanceof HasValue) { ((HasValue<?,?>) comp).addValueChangeListener(evt -> { //do sth with evt }); } }

HasValue Javadoc

Excepción (solo 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

Solución:

ValueChangeListener<ValueChangeEvent<?>> listener = evt -> { // do sth with evt }; ((HasValue<?,?>) comp).addValueChangeListener(listener);

Sistema:
OS: Windows 10
IDE: Eclipse 2018-12 (4.10.0)
Java (compilar): ecj
Java (servidor web): JDK 11.0.2
Servidor web: Wildfly 15