JDK is Oracle' JDK 1.8u65 but the problem has been seen with "as low as" 1.8u25 as well.
Here is the full SSCCE:
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); } }
This code compiles; but it fails at runtime:
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
Fixing it in code is "easy"; in the main method, you just have to:
// Note the <X> Stream.<X>of(E1.INSTANCE, E2.INSTANCE).forEach(X::x);
EDIT There is in fact a second way, as mentioned in the accepted answer... Replace the method reference with a lambda:
Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> x.x());
So, uh. What happens here? Why does the initial code compile in the first place? I'd have expected the compiler to notice that the method reference was not on anything Enum<?>
but on X
, but no...
What am I missing? Is this a bug in the compiler? A misunderstanding of mine?
It seems you've hit JDK-8141508, which is indeed a bug of javac
when dealing with intersection types and method-references. It is scheduled to be fixed in Java 9.
Quoting a mail from Remi Forax:
javac has trouble with intersection type that are target type of a lambda and method reference, Usually when there is an intersection type, javac substitute it by the first type of the intersection type and add cast when necessary.
Let suppose we have this code,
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()); } }
Currently, javac generates a method reference on J::foo with an invokedynamic that takes an I as parameter, hence it fails at runtime. javac should de-sugar t::foo into a lambda that take an I and then add a cast to J like for a call to a method of an intersection type.
So the workaround is to use a lambda instead,
Runnable r = t -> t.foo();
I've already seen this bug somewhere but was not able to find a corresponding bug report in the database :(
In your code, the Stream created by Stream.of(E1.INSTANCE, E2.INSTANCE)
is of type Stream<Enum<?>&Foo.X>
, which combines all the elements of the bug: intersecting types and method-references.
As noted by Remi Forax, a work-around would be:
Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> x.x());
i.e. using an explicit lambda expression instead of a method-reference.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With