枚举,接口和(Java 8)lambdas:代码编译但在运行时失败;这是预期的吗? [英] Enum, interfaces and (Java 8) lambdas: code compiles but fails at runtime; is this expected?

查看:305
本文介绍了枚举,接口和(Java 8)lambdas:代码编译但在运行时失败;这是预期的吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

JDK is Oracle' JDK 1.8u65 but the problem has been seen with "as low as" 1.8u25 as well.

这是完整的SSCCE:

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

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

Fixing it in code is "easy"; in the main method, you just have to:

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

编辑实际上还有第二种方式,如公认的那样回答...用lambda替换方法引用:

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

所以,呃。这里发生了什么?为什么初始代码首先编译?我原本期望编译器注意到方法引用不在任何 Enum<?> 但在 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?

推荐答案

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

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.

引用来自Remi Forax的邮件


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

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.

假设我们有这个代码,

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转化为一个lambda,它接受一个I​​,然后向J添加一个强制转换,就像调用一个交集类型的方法一样。

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.

因此解决方法是改为使用lambda,

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 :(

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

In your code, the Stream created by Stream.of(E1.INSTANCE, E2.INSTANCE) is of type Stream<Enum<?>&Foo.X>, which combines all the elements of the bug: intersecting types and method-references.

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

As noted by Remi Forax, a work-around would be:

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

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

i.e. using an explicit lambda expression instead of a method-reference.

这篇关于枚举,接口和(Java 8)lambdas:代码编译但在运行时失败;这是预期的吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆