枚举,接口和(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
,它结合了bug的所有元素:相交类型和方法引用。
如Remi Forax所述,解决方法是:
Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> xx());
即使用显式的lambda表达式而不是方法引用。