usar porque interfaces implementacion expresiones ejemplos aws java generics lambda package-private

java - interfaces - porque usar lambda aws



La expresión Lambda falla con un java.lang.BootstrapMethodError en tiempo de ejecución (1)

Por lo que veo, JVM hace todo bien.

Cuando el método de apply se declara en Applicable , pero no en SomeApplicable , la clase anónima debería funcionar, y la lambda no debería. Vamos a examinar el bytecode.

Prueba de clase anónima $ 1

public void apply(a.SomeApplicable); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String a 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return public void apply(a.Applicable); Code: 0: aload_0 1: aload_1 2: checkcast #5 // class a/SomeApplicable 5: invokevirtual #6 // Method apply:(La/SomeApplicable;)V 8: return

javac genera tanto la implementación del método de interfaz apply(Applicable) como el método de invalidación apply(SomeApplicable) . Ninguno de los métodos se refiere a una interfaz inaccesible. Applicable , excepto en la firma del método. Es decir, la interfaz Applicable no se resuelve (JVMS §5.4.3) en ninguna parte del código de la clase anónima.

Tenga en cuenta que se puede llamar con éxito a apply(Applicable) desde Test , porque los tipos en la firma del método no se resuelven durante la resolución de la instrucción invokeinterface (JVMS §5.4.3.4) .

Lambda

Una instancia de lambda se obtiene mediante la ejecución de un invokedynamic byte invokedynamic con el método de arranque LambdaMetafactory.metafactory :

BootstrapMethods: 0: #36 invokestatic java/lang/invoke/LambdaMetafactory.metafactory Method arguments: #37 (La/Applicable;)V #38 invokestatic b/Test.lambda$main$0:(La/SomeApplicable;)V #39 (La/SomeApplicable;)V

Los argumentos estáticos utilizados para construir lambda son:

  1. MethodType de la interfaz implementada: void (a.Applicable) ;
  2. Dirigir el Método Manejar a la implementación;
  3. Tipo de método efectivo de la expresión lambda: void (a.SomeApplicable) .

Todos estos argumentos se resuelven durante el proceso de arranque dinámico invocado (JVMS §5.4.3.6) .

Ahora el punto clave: para resolver un MethodType se resuelven todas las clases e interfaces dadas en su descriptor de método (JVMS §5.4.3.5) . En particular, JVM intenta resolver un. a.Applicable en nombre de la clase de Test y falla con IllegalAccessError . Luego, de acuerdo con la especificación de invokedynamic , el error se envuelve en BootstrapMethodError .

Método de puente

Para IllegalAccessError , debe agregar explícitamente un método de puente en la interfaz SomeApplicable accesible al SomeApplicable :

public interface SomeApplicable extends Applicable<SomeApplicable> { @Override void apply(SomeApplicable self); }

En este caso, lambda implementará el método apply(SomeApplicable) lugar de apply(Applicable) . La correspondiente instrucción invokedynamic se referirá a (La/SomeApplicable;)V MethodType, que se resolverá con éxito.

Nota: no es suficiente cambiar solo la interfaz de SomeApplicable . Tendrá que SomeApplicable a compilar la Test con la nueva versión de SomeApplicable para generar una invokedynamic con los MethodTypes adecuados. He verificado esto en varios JDK desde 8u31 hasta el último 9-ea, y el código en cuestión funcionó sin errores.

En un paquete ( a ) tengo dos interfaces funcionales:

package a; @FunctionalInterface interface Applicable<A extends Applicable<A>> { void apply(A self); }

-

package a; @FunctionalInterface public interface SomeApplicable extends Applicable<SomeApplicable> { }

El método de apply en la superinterfaz se toma a self como una A porque, de lo contrario, si se utilizara Applicable<A> , el tipo no sería visible fuera del paquete y, por lo tanto, el método no podría implementarse.

En otro paquete ( b ), tengo la siguiente clase de Test :

package b; import a.SomeApplicable; public class Test { public static void main(String[] args) { // implement using an anonymous class SomeApplicable a = new SomeApplicable() { @Override public void apply(SomeApplicable self) { System.out.println("a"); } }; a.apply(a); // implement using a lambda expression SomeApplicable b = (SomeApplicable self) -> System.out.println("b"); b.apply(b); } }

La primera implementación utiliza una clase anónima y funciona sin problemas. El segundo, por otro lado, compila bien pero falla en el tiempo de ejecución al lanzar un java.lang.BootstrapMethodError causado por un java.lang.IllegalAccessError cuando intenta acceder a la interfaz Applicable .

Exception in thread "main" java.lang.BootstrapMethodError: java.lang.IllegalAccessError: tried to access class a.Applicable from class b.Test at b.Test.main(Test.java:19) Caused by: java.lang.IllegalAccessError: tried to access class a.Applicable from class b.Test ... 1 more

Creo que tendría más sentido si la expresión lambda funcionara igual que la clase anónima o cometiera un error de compilación. Entonces, me pregunto qué está pasando aquí.

Intenté quitar la superinterfaz y declarar el método dentro de SomeApplicable como esto:

package a; @FunctionalInterface public interface SomeApplicable { void apply(SomeApplicable self); }

Obviamente, esto hace que funcione, pero nos permite ver qué hay de diferente en el código de bytes.

El método lambda$0 sintético de lambda$0 compilado a partir de la expresión lambda parece idéntico en ambos casos, pero podría detectar una diferencia en los argumentos del método bajo los métodos bootstrap.

Bootstrap methods: 0 : # 58 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #59 (La/Applicable;)V #62 invokestatic b/Test.lambda$0:(La/SomeApplicable;)V #63 (La/SomeApplicable;)V

El #59 cambia de (La/Applicable;)V a (La/SomeApplicable;)V

Realmente no sé cómo funciona la metafactor lambda, pero creo que esto podría ser una diferencia clave.

También intenté declarar explícitamente el método de apply en SomeApplicable como esta:

package a; @FunctionalInterface public interface SomeApplicable extends Applicable<SomeApplicable> { @Override void apply(SomeApplicable self); }

Ahora el método apply(SomeApplicable) realmente existe y el compilador genera un método de bridge para apply(Applicable) . Todavía el mismo error se produce en tiempo de ejecución.

A nivel de bytecode ahora usa LambdaMetafactory.altMetafactory lugar de LambdaMetafactory.metafactory :

Bootstrap methods: 0 : # 57 invokestatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; Method arguments: #58 (La/SomeApplicable;)V #61 invokestatic b/Test.lambda$0:(La/SomeApplicable;)V #62 (La/SomeApplicable;)V #63 4 #64 1 #66 (La/Applicable;)V