Enum, interfaces and (Java 8) lambdas: code compiles but fails at runtime; is this expected?

匿名 (未验证) 提交于 2019-12-03 01:23:02

问题:

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  Stream.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?

回答1:

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  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&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.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!