【问题标题】:Enum, interfaces and (Java 8) lambdas: code compiles but fails at runtime; is this expected?枚举、接口和 (Java 8) lambdas:代码编译但在运行时失败;这是预期的吗?
【发布时间】:2016-02-28 18:38:38
【问题描述】:

JDK 是 Oracle 的 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

在代码中修复它很“容易”;在 main 方法中,您只需:

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

编辑实际上还有第二种方法,正如接受的答案中提到的那样...用 lambda 替换方法引用:

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

所以,嗯。这里会发生什么?为什么最初的代码会编译?我原以为编译器会注意到方法引用不在任何 Enum&lt;?&gt; 上,而是在 X 上,但没有...

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

【问题讨论】:

  • 可能是一个相关的javac 错误:JDK-8141508 虽然它似乎处理相交类型。
  • 它是一个相交类型。根据我的 Eclipse,Stream.of(E1.INSTANCE,E2.INSTANCE) 的推断类型是 Stream&lt;Enum&lt;?&gt;&amp;Foo.X&gt;。
  • @Tunaki Easy:您将其分配给一个不相关的变量,例如int i = Stream.of(...),然后查看错误消息抱怨的内容。 :-)
  • @RealSkeptic 不错,我明白了! :) 我猜这与上面的错误相同。
  • @fge 我会这么认为。它报告了两种不同类型的事实——一种是悬停在of 或forEach 上时,它只是说它是一个枚举流,另一种是当我使用上面的技巧时意味着编译器本身有点混乱。我认为其中一个允许X::x,但另一个写入引用错误类型的字节码。

标签: java java-8 type-inference


【解决方案1】:

您似乎遇到了JDK-8141508,这确实是javac 在处理交集类型和方法引用时的一个错误。它计划在 Java 9 中修复。

引用a mail from Remi Forax:

javac 对于作为 lambda 目标类型和方法引用的交集类型有问题, 通常当存在交集类型时,javac 将其替换为交集类型的第一个类型,并在必要时添加强制转换。

假设我们有这段代码,

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

目前,javac 在 J::foo 上生成一个方法引用,其中带有一个以 I 作为参数的 invokedynamic,因此它在运行时失败。 javac 应该将 t::foo 脱糖成一个接受 I 的 lambda,然后像调用一个交集类型的方法一样向 J 添加一个强制转换。

所以解决方法是改用 lambda,

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

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

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

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

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

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

【讨论】:

  • 是的,这个变通办法也有效(在你回答之前我已经测试过了,并且在你链接到这个错误之后也偶然发现了这封邮件)。好挖!谢谢!
  • 我为此在 Jetbrains 上打开了一个增强请求:youtrack.jetbrains.com/issue/IDEA-148528。我想可以为 Eclipse 打开一个类似的请求 :)
  • @fge 是的,我搜索了他们的 Bugzilla,但找不到任何东西。
  • Remi Forax 没有找到的旧错误是JDK-8058112。也许它已经被关闭的事实阻碍了找到它。请注意,the related older question 的示例仍然无法使用最近的 JDK...
猜你喜欢
  • 2015-01-13
  • 2021-12-26
  • 2014-09-08
  • 1970-01-01
  • 2013-05-23
  • 2013-05-07
  • 2018-09-23
  • 1970-01-01
  • 2021-08-15
相关资源
最近更新 更多