los jre instalacion error descargar conmutadores java generics lambda java-8

instalacion - jre java 32 bits



LambdaConversionException con genéricos: error de JVM? (4)

Tengo un código con una referencia de método que compila bien y falla en tiempo de ejecución.

La excepción es esta:

Caused by: java.lang.invoke.LambdaConversionException: Invalid receiver type class redacted.BasicEntity; not a subtype of implementation type interface redacted.HasImagesEntity at java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:233) at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:303) at java.lang.invoke.CallSite.makeSite(CallSite.java:289)

La clase que desencadena la excepción:

class ImageController<E extends BasicEntity & HasImagesEntity> { void doTheThing(E entity) { Set<String> filenames = entity.getImages().keySet().stream() .map(entity::filename) .collect(Collectors.toSet()); } }

La excepción se produce al intentar resolver entity::filename . filename() se declara en HasImagesEntity . Por lo que puedo decir, obtengo la excepción porque la BasicEntity de E es BasicEntity y la JVM no (¿no?) Considera otros límites en E.

Cuando reescribo la referencia del método como una lambda trivial, todo está bien. Me parece realmente sospechoso que una construcción funcione como se esperaba y su equivalente semántico explota.

¿Podría esto posiblemente estar en la especificación? Estoy tratando de encontrar una manera para que esto no sea un problema en el compilador o en el tiempo de ejecución, y no se me ocurrió nada.


Acabo de solucionar este problema en JDK9 y JDK8u45. Ver este error El cambio tardará un poco en filtrarse en las compilaciones promocionadas. Dan acaba de señalarme esta pregunta de , así que agrego esta nota. Cuando encuentre errores, envíelos.

Abordé esto haciendo que el compilador creara un puente, como es el enfoque para muchos casos de referencias de métodos complejos. También estamos examinando las implicaciones de las especificaciones.


Aquí hay un ejemplo simplificado que reproduce el problema y usa solo clases centrales de Java:

public static void main(String[] argv) { System.out.println(dummy("foo")); } static <T extends Serializable&CharSequence> int dummy(T value) { return Optional.ofNullable(value).map(CharSequence::length).orElse(0); }

Su suposición es correcta, la implementación específica de JRE recibe el método de destino como MethodHandle que no tiene información sobre tipos genéricos. Por lo tanto, lo único que ve es que los tipos sin formato no coinciden.

Al igual que con muchas construcciones genéricas, se requiere una conversión de tipo en el nivel de código de byte que no aparece en el código fuente. Dado que LambdaMetafactory requiere explícitamente un identificador de método directo , una referencia de método que encapsula dicho tipo de MethodHandle no se puede pasar como MethodHandle a la fábrica.

Hay dos formas posibles de lidiar con eso.

La primera solución sería cambiar la LambdaMetafactory para confiar en MethodHandle si el tipo de receptor es una interface e insertar el tipo de MethodHandle requerido por sí mismo en la clase lambda generada en lugar de rechazarlo. Después de todo, ya es similar para los parámetros y los tipos de retorno.

Alternativamente, el compilador se encargaría de crear un método auxiliar sintético que encapsulara el tipo de conversión y la llamada al método, como si hubiera escrito una expresión lambda. Esta no es una situación única. Si usa una referencia de método a un método varargs o una creación de matriz como, por ejemplo, String[]::new , no se pueden expresar como manejadores de métodos directos y terminan en métodos auxiliares sintéticos.

En cualquier caso, podemos considerar el comportamiento actual como un error. Pero obviamente, los compiladores y los desarrolladores de JRE deben acordar de qué manera se debe manejar antes de que podamos decir de qué lado reside el error.


Encontré una solución para esto: cambiar el orden de los genéricos. Por ejemplo, use la class A<T extends B & C> donde necesita acceder a un método B , o use la class A<T extends C & B> si necesita acceder a un método C Por supuesto, si necesita acceso a métodos de ambas clases, esto no funcionará. Esto me pareció útil cuando una de las interfaces era una interfaz de marcador como Serializable .

En cuanto a arreglar esto en el JDK, la única información que pude encontrar fueron algunos errores en el rastreador de errores de openjdk que están marcados como resueltos en la versión 9, lo cual es bastante inútil.


Este error no está completamente solucionado. Me encontré con una LambdaConversionException en 1.8.0_72 y vi que hay informes de errores abiertos en el sistema de seguimiento de errores de Oracle: link1 , link2 .

(Editar: se informa que los errores vinculados están cerrados en JDK 9 b93)

Como solución alternativa simple, evito los identificadores de métodos. Entonces en lugar de

.map(entity::filename)

hago

.map(entity -> entity.filename())

Aquí está el código para reproducir el problema en Debian 3.11.8-1 x86_64.

import java.awt.Component; import java.util.Collection; import java.util.Collections; public class MethodHandleTest { public static void main(String... args) { new MethodHandleTest().run(); } private void run() { ComponentWithSomeMethod myComp = new ComponentWithSomeMethod(); new Caller<ComponentWithSomeMethod>().callSomeMethod(Collections.singletonList(myComp)); } private interface HasSomeMethod { void someMethod(); } static class ComponentWithSomeMethod extends Component implements HasSomeMethod { @Override public void someMethod() { System.out.println("Some method"); } } class Caller<T extends Component & HasSomeMethod> { public void callSomeMethod(Collection<T> components) { components.forEach(HasSomeMethod::someMethod); // <-- crashes // components.forEach(comp -> comp.someMethod()); <-- works fine } } }