Ссылка на конструктор для внутреннего класса завершается с VerifyError во время выполнения

Я создаю поставщика для конструктора внутреннего класса, используя лямбду 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));
        }
    }
}

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 конструктор.

Но я не мог прийти к точной причине. Приведенные выше рассуждения вполне могут быть ошибочными. Но я только пришел к этому после того, как начал работать.

Возможно, вы захотите пройти лямбда-перевод, чтобы понять внутреннюю работу. Я до сих пор не очень понимаю, как переводятся ссылки на лямбды и методы.

Я нашел тему в лямбда-рассылке о подобной проблеме. Кроме того, этот пост также связан.

Следующие 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, чтобы увидеть методы Bootstrap для #0, #1и т.д. Я думаю, мы можем определенно сказать, что это ошибка. Возможно, вы можете подать один.

Другие вопросы по тегам