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

fge*_*fge 21 java type-inference java-8

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

// Note the <X>
Stream.<X>of(E1.INSTANCE, E2.INSTANCE).forEach(X::x);
Run Code Online (Sandbox Code Playgroud)

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

Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> x.x());
Run Code Online (Sandbox Code Playgroud)

所以,呃.这里发生了什么?为什么初始代码首先编译?我原本期望编译器注意到方法引用不是在任何东西Enum<?>X,而是在......

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

Tun*_*aki 19

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

引用Remi Forax发来的邮件:

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

假设我们有这个代码,

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());
      }
  }
Run Code Online (Sandbox Code Playgroud)

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

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

Runnable r = t -> t.foo();
Run Code Online (Sandbox Code Playgroud)

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

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

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

Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> x.x());
Run Code Online (Sandbox Code Playgroud)

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