【问题标题】:Constructor reference for inner class fails with VerifyError at runtime内部类的构造函数引用在运行时因 VerifyError 而失败
【发布时间】:2014-03-30 00:54:49
【问题描述】:

我正在使用 lambda ctx -> new SpectatorSwitcher(ctx) 为内部类构造函数创建供应商。 IntelliJ 建议我将其更改为 SpectatorSwitcher::new。 SpectatorSwitcher 是我正在使用的类的非静态内部类。建议的代码编译良好(使用 maven),但执行时出现以下 VerifyError:

Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    Test.lambda$runTest$8(LTest$Worker;)V @2: invokedynamic
  Reason:
    Type 'Test$Worker' (current frame, stack[1]) is not assignable to 'Test'
  Current Frame:
    bci: @2
    flags: { }
    locals: { 'Test$Worker' }
    stack: { 'Test$Worker', 'Test$Worker' }
  Bytecode:
    0000000: 2a2a ba00 0b00 00b6 000c b1            

    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2688)
    at java.lang.Class.getMethod0(Class.java:2937)
    at java.lang.Class.getMethod(Class.java:1771)
    at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)

为什么 javac / maven 在编译时没有失败,但仍然产生无效的字节码?

编辑:问题似乎远比简单的调用复杂,这是重现它所需的代码:

import java.util.function.Function;

/**
 * @author Yawkat
 */
public class Test {
    public static void main(String[] args) { new Test().runTest(); }

    private void runTest() {
        Worker worker = new Worker();
        run(() -> worker.print(field -> new SomeClass(field)));
        run(() -> worker.print(SomeClass::new));
    }

    private void run(Runnable runnable) {
        runnable.run();
    }

    private class SomeClass {
        final Object field;

        SomeClass(Object field) {
            this.field = field;
        }
    }

    private static class Worker {
        void print(Function<Object, Object> i) {
            System.out.println(i.apply(null));
        }
    }
}

【问题讨论】:

  • 你能给我们一个可重现的小代码sn-p吗?
  • @SotiriosDelimanolis 我不得不做更多的研究,因为所需的代码比我想象的要复杂得多。我加了。
  • 看起来像一个 JVM 错误。请考虑提交错误报告。
  • 这是一个 javac 错误,而不是 JVM 错误。请参阅有关 Rohit Jain 答案的评论主题。

标签: java lambda inner-classes java-8 constructor-reference


【解决方案1】:

即使在我对字节码进行了近一个小时的研究之后,我仍然无法就为什么会发生这种情况得出合理的结论。令人惊讶的是,将您的方法更改为:

private void runTest() {
    Worker worker = new Worker();
    run(() -> worker.print(field -> new SomeClass(field)));
    Function<Object, Object> function = SomeClass::new;
    run(() -> worker.print(function));
}

工作正常。此外,摆脱run() 方法调用,只调用worker.print()

private void runTest() {
    Worker worker = new Worker();
    worker.print(field -> new SomeClass(field));
    worker.print(SomeClass::new);
}

也可以。

看起来,在您的情况下使用构造函数引用无法将 Test 类的封闭实例传递给所需的 SomeClass 构造函数。虽然这里的两种情况都能够将Test 实例传递给SomeClass 构造函数。

但我无法找到确切的原因。上述推理很可能是错误的。但我只是在了解了这些工作方法之后才想到这一点。

您可能想通过lambda translation 了解内部工作原理。我仍然不太清楚 lambda 和方法引用是如何翻译的。

我发现了一个关于类似问题的thread in lambda mailing list。另外,this SO post 也是相关的。

以下runtTest()方法:

public void runTest() {
    Worker worker = new Worker();
    run(() -> worker.print((field) -> new SomeClass(field)));
    run(() -> worker.print(SomeClass::new));

    Function<Object, Object> func = SomeClass::new;
    run(() -> worker.print(func));

    worker.print(SomeClass::new);
}

被编译成以下字节码:

  public void runTest();
    Code:
       0: new           #2                  // class SO$Worker
       3: dup
       4: invokespecial #3                  // Method SO$Worker."<init>":()V
       7: astore_1
       8: aload_0
       9: aload_0
      10: aload_1
      11: invokedynamic #4,  0              // InvokeDynamic #0:run:(LSO;LSO$Worker;)Ljava/lang/Runnable;
      16: invokevirtual #5                  // Method run:(Ljava/lang/Runnable;)V
      19: aload_0
      20: aload_1
      21: invokedynamic #6,  0              // InvokeDynamic #1:run:(LSO$Worker;)Ljava/lang/Runnable;
      26: invokevirtual #5                  // Method run:(Ljava/lang/Runnable;)V
      29: aload_0
      30: invokedynamic #7,  0              // InvokeDynamic #2:apply:(LSO;)Ljava/util/function/Function;
      35: astore_2
      36: aload_0
      37: aload_1
      38: aload_2
      39: invokedynamic #8,  0              // InvokeDynamic #3:run:(LSO$Worker;Ljava/util/function/Function;)Ljava/lang/Runnable;
      44: invokevirtual #5                  // Method run:(Ljava/lang/Runnable;)V
      47: aload_1
      48: aload_0
      49: invokedynamic #7,  0              // InvokeDynamic #2:apply:(LSO;)Ljava/util/function/Function;
      54: invokevirtual #9                  // Method SO$Worker.print:(Ljava/util/function/Function;)V
      57: return

我只能看到第二个 run() 方法调用没有通过 LSO 参数,而其他人确实通过了它。您可以运行命令 - javap -c -s -verbose Test,查看 #0#1 等的引导方法。我想我们可以肯定地说这是一个错误。也许你可以提交一份。

【讨论】:

  • 我已经为此提交了一份错误报告。
  • @yawkat 请在您的问题中添加错误报告的链接。
  • @Kobi 谢谢。固定:)
  • @yawkat 和 Rohit,感谢您跟踪此问题并提交错误。修补这种代码,很容易让编译器因 NPE 而崩溃。这里的 javac 肯定有一个错误。看起来 JDK 错误数据库中已经有一个错误:bugs.openjdk.java.net/browse/JDK-8037404
猜你喜欢
  • 2013-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-29
  • 2014-10-23
相关资源
最近更新 更多