java lambda inner-classes java-8 constructor-reference

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



lambda inner-classes (1)

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


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.