枚举,接口和(Java 8)lambdas:代码编译但在运行时失败; 这是预期的吗?

JDK是甲骨文的JDK 1.8u65,但问题是“低至”1.8u25。

这是完整的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); } } 

这段代码编译; 但它在运行时失败:

 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 

在代码中修复它是“容易的”; 在主要方法中,您只需:

 // Note the  Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(X::x); 

编辑实际上有第二种方法,如接受的答案中所述……用lambda替换方法引用:

 Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> xx()); 

所以,呃。 这里发生了什么? 为什么初始代码首先编译? 我原本期望编译器注意到方法引用不在任何Enum上,而是在X ,但是没有……

我错过了什么? 这是编译器中的错误吗? 对我的误解?

看来你已经遇到了JDK-8141508 ,这在处理交集类型和方法引用时确实是javac一个bug。 它计划在Java 9中修复。

引用Remi Forax发来的邮件 :

javac有作为lambda的目标类型和方法引用的交集类型有问题。通常当有交集类型时,javac用交集类型的第一种类型替换它,并在必要时添加转换。

假设我们有这个代码,

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

目前,javac使用一个以I为参数的invokedynamic在J :: foo上生成方法引用,因此它在运行时失败。 javac应该将t :: foo解压缩为一个带有I的lambda,然后将一个强制类型添加到J中,就像调用一个交集类型的方法一样。

所以解决方法是使用lambda代替,

 Runnable r = t -> t.foo(); 

我已经在某个地方看到了这个bug,但是无法在数据库中找到相应的错误报告:(

在您的代码中, Stream.of(E1.INSTANCE, E2.INSTANCE)创建的Stream的类型为Stream&Foo.X> ,它结合了bug的所有元素:相交类型和方法引用。

如Remi Forax所述,解决方法是:

 Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> xx()); 

即使用显式的lambda表达式而不是方法引用。