values - Enum, interfaces y(Java 8) lambdas: el código se compila pero falla en el tiempo de ejecución; es esto esperado?
java enum to list (1)
JDK es Oracle ''JDK 1.8u65 pero el problema se ha visto con "tan bajo como" 1.8u25 también.
Aquí está el SSCCE completo:
public final class Foo
{
private interface X
{
default void x()
{
}
}
private enum E1
implements X
{
INSTANCE,
;
}
private enum E2
implements X
{
INSTANCE,
;
}
public static void main(final String... args)
{
Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(X::x);
}
}
Este código compila; pero falla en tiempo de ejecución:
Exception in thread "main" java.lang.BootstrapMethodError: call site initialization exception
at java.lang.invoke.CallSite.makeSite(CallSite.java:341)
at java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:307)
at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:297)
at com.github.fge.grappa.debugger.main.Foo.main(Foo.java:38)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.invoke.LambdaConversionException: Invalid receiver type class java.lang.Enum; not a subtype of implementation type interface com.github.fge.grappa.debugger.main.Foo$X
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:302)
... 8 more
Repararlo en el código es "fácil"; En el método principal, solo tienes que:
// Note the <X>
Stream.<X>of(E1.INSTANCE, E2.INSTANCE).forEach(X::x);
EDITAR De hecho, hay una segunda forma, como se menciona en la respuesta aceptada ... Reemplace la referencia del método con un lambda:
Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> x.x());
Entonces, uh. ¿Qué pasa aquí? ¿Por qué el código inicial se compila en primer lugar? Habría esperado que el compilador notara que la referencia del método no estaba en nada Enum<?>
Sino en X
, pero no ...
¿Qué me estoy perdiendo? ¿Es este un error en el compilador? ¿Un malentendido mío?
Parece que has golpeado JDK-8141508 , que de hecho es un error de javac
cuando se trata de tipos de intersección y referencias de métodos. Está programado para ser arreglado en Java 9.
Citando un correo de Remi Forax :
javac tiene problemas con el tipo de intersección que es el tipo de destino de un lambda y la referencia del método. Normalmente, cuando hay un tipo de intersección, javac lo sustituye por el primer tipo del tipo de intersección y agrega conversión cuando sea necesario.
Supongamos que tenemos este código,
public class Intersection { interface I { } interface J { void foo(); } static <T extends I & J> void bar(T t) { Runnable r = t::foo; } public static void main(String[] args) { class A implements I, J { public void foo() {} } bar(new A()); } }
Actualmente, javac genera una referencia de método en J :: foo con una dinámica invocada que toma una I como parámetro, por lo que falla en el tiempo de ejecución. javac debería de-sugar t :: foo en un lambda que tome una I y luego agregue un molde a J como para una llamada a un método de tipo de intersección.
Así que la solución es usar un lambda en su lugar,
Runnable r = t -> t.foo();
Ya he visto este error en alguna parte, pero no pude encontrar un informe de error correspondiente en la base de datos :(
En su código, el Stream creado por Stream.of(E1.INSTANCE, E2.INSTANCE)
es del tipo Stream<Enum<?>&Foo.X>
, que combina todos los elementos del error: tipos de intersección y referencias de métodos.
Como señaló Remi Forax, una solución alternativa sería:
Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> x.x());
es decir, utilizando una expresión lambda explícita en lugar de una referencia de método.