java La referencia del constructor para la clase interna falla con VerifyError en tiempo de ejecución



lambda inner-classes (1)

Incluso después de pegar mi cabeza en el código de acceso durante casi una hora, no he podido llegar a una conclusión razonable sobre por qué sucede esto. Sorprendentemente, cambiando su método a esto:

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

funciona bien. Además, deshacerse de la invocación del método run() y simplemente llamar a worker.print() :

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

tambien funciona

Parece que usar la referencia del constructor como en su caso no puede pasar la instancia SomeClass la clase Test al constructor SomeClass que se requiere. Mientras que los dos casos aquí son capaces de pasar la instancia de Test al constructor SomeClass .

Pero no pude llegar a la razón exacta. El razonamiento anterior bien podría estar equivocado. Pero acabo de llegar a eso después de llegar al enfoque de trabajo.

Es posible que desee ir a través de la traducción lambda , para comprender el trabajo interno. Todavía no tengo muy claro cómo se traducen las lambdas y las referencias de los métodos.

Encontré un hilo en la lista de correo lambda sobre un problema similar. Además, esta publicación de SO también está relacionada.

El siguiente método 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);
}

Se compila al siguiente bytecode:

  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

Puedo ver que solo la segunda invocación del método run() no pasa el argumento LSO , mientras que otros lo pasan. Puede ejecutar el comando - javap -c -s -verbose Test , para ver los métodos Bootstrap para #0 , #1 , etc. Supongo que definitivamente podemos decir que esto es un error. Quizás puedas archivar uno.

Estoy creando un proveedor para un constructor de clase interna usando lambda ctx -> new SpectatorSwitcher(ctx) . IntelliJ sugirió que lo cambie a SpectatorSwitcher::new lugar. SpectatorSwitcher es una clase interna no estática de la clase en la que estoy trabajando. El código sugerido se compila bien (usando maven) pero obtengo el siguiente VerifyError en la ejecución:

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)

¿Por qué javac / maven no falla al compilar pero sigue generando un código de byte no válido?

Edición: el problema parece ser mucho más complejo que la simple llamada, este es el código necesario para reproducirlo:

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




constructor-reference