当前位置: 首页 > 面试题库 >

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

堵景天
2023-03-14
问题内容

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

用代码修复它很容易。在主要方法中,您只需要:

// 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<?>X,而是在上,但是没有…

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


问题答案:

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

引用来自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 Stream.of(E1.INSTANCE, E2.INSTANCE)是type
Stream<Enum<?>&Foo.X>,它结合了bug的所有元素:相交的类型和方法引用。

正如Remi Forax所指出的,解决方法是:

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

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



 类似资料:
  • JDK是Oracle的JDK 1.8u65,但问题也出现在“低至”1.8u25的地方。 以下是完整的SSCCE: 本代码编译;但在运行时失败: 我错过了什么?这是编译器中的bug吗?对我的误解?

  • 问题内容: 我已经对这个程序进行了数小时的故障排除,尝试了几种配置,但是没有运气。它是用Java编写的,有33个错误(从之前的50个降低) 源代码: 错误日志(在JCreator中编译): 我觉得这是一个基本错误,但似乎找不到。如果有什么不同,我正在使用JCreator进行编译,并且一切都已正确安装。 更新:我已修复所涉及的错误(类声明和不正确的导入语句(有人回去并删除了一些分号)) 工作代码:

  • Java8引入了重要的新语言特性,如lambda表达式。 语言中的这些变化是否伴随着编译字节码中的重大变化,从而阻止它在不使用某些反向翻译器的情况下在Java7虚拟机上运行?

  • 我正在使用java 11处理一些依赖项并编译到旧版本。我将一个依赖项迁移到Java11并正常工作,但我们仍然必须在Java8上运行Tomcat 7或8。是否可以使用标志来编译使用,或并在8上运行的代码? 发布标志表明应该可以: --发布版本 针对特定 VM 版本的公共、受支持和记录的 API 进行编译。支持的版本目标为 6、7、8 和 9。 这个项目是一个依赖项,独立运行在SprinBoot2.1

  • 问题内容: 我需要知道此代码的输出。但这不起作用。也许代码是错误的。我仍在学习如何使用Java,我尝试修复了几个小时,但还是没有运气。 这是代码: 谁能告诉我代码中有什么错误或遗漏吗? 问题答案: 例如: 另请注意,这可能无法打印您期望的结果。它实际上会打印: 为什么?构造函数总是链接到超类。

  • 问题内容: 命令可以运行已编译的Scala代码吗?如果是这样,我们为什么要使用排他命令? 问题答案: 如果您在类路径中包含了Scala的所有必要运行时库(scala-library.jar,scala-swing.jar …),则可以运行Scala生成的字节代码。scala命令自动执行此操作,并支持特定于Scala的命令行参数。