tutorial funciones expresiones explicacion ejemplos anonimas java lambda java-8 bytecode

expresiones - java funciones lambda ejemplos



¿Se puede inspeccionar el código de byte de un lambda de Java 8 en tiempo de ejecución? (4)

Después de todo, encontré una forma de obtener un archivo de clase de una expresión lambda que es bastante confiable (pero, por supuesto, todavía depende de los detalles de implementación). Para una implementación experimental, ahora estoy usando un agente Java que permite la retransformación de clases pero que a su vez se implementa como un no-op, solo me interesa la matriz binaria que se entrega como un argumento.

Después de obtener una instancia de Instrumentation través del agente Java registrado, uno registra un ClassFileTransformer que luego se notifica a través de la representación binaria una vez que se retransforma lambdaInstance.getClass() . Esta es, por supuesto, una solución bastante intrincada y podría romperse una vez que se cambie la implementación interna de lambdas en el futuro.

Desafortunadamente, no encontré ninguna documentación sobre cómo se supone que se comporta un agente con la retransformación de clase de la clase sintética de una expresión lambda en una JVM compatible con los estándares.

Si tienes una clase anónima como

Predicate<String> isEmpty = new Predicate<String>() { public boolean test(String t) { return t.isEmpty(); } };

Una biblioteca a la que se le pasa la referencia a isEmpty puede inspeccionar el código de bytes para ver qué hace y posiblemente manipularlo. ¿Hay alguna manera de hacer esto para las lambdas?

Predicate<String> isEmpty = String::isEmpty;

Por ejemplo, di tener este código de byte y código

public class Main { public static void test(Predicate<String> tester) { System.out.println("tester.getClass()= " + tester.getClass()); System.out.println("tester.getClass().getClassLoader()="+ tester.getClass().getClassLoader()); } public static void main(String... args) { Predicate<String> isEmpty = String::isEmpty; test(isEmpty); } } $ javap -cp . -c -private Main.class Compiled from "Main.java" public class Main { public Main(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void test(java.util.function.Predicate<java.lang.String>); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 10: ldc #5 // String tester.getClass()= 12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: aload_0 16: invokevirtual #7 // Method java/lang/Object.getClass:()Ljava/lang/Class; 19: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; 22: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 25: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 28: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 31: new #3 // class java/lang/StringBuilder 34: dup 35: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 38: ldc #11 // String tester.getClass().getClassLoader()= 40: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 43: aload_0 44: invokevirtual #7 // Method java/lang/Object.getClass:()Ljava/lang/Class; 47: invokevirtual #12 // Method java/lang/Class.getClassLoader:()Ljava/lang/ClassLoader; 50: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; 53: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 56: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 59: return public static void main(java.lang.String...); Code: 0: invokedynamic #13, 0 // InvokeDynamic #0:test:()Ljava/util/function/Predicate; 5: astore_1 6: aload_1 7: invokestatic #14 // Method test:(Ljava/util/function/Predicate;)V 10: return }

Con una referencia al tester en test ¿cómo puedo encontrar el método que se llama?


Encontré otra forma: parcheando el InnerClassLambdaMetafactory es posible agregar una anotación a las clases lambda que apunta al método de implementación. Esto es posible en tiempo de ejecución cargando un agente y retransformando la metafactoria. Ver mi blog para más detalles.


La respuesta simple es: no puedes. Hay una respuesta relacionada de Brian Goetz sobre este asunto. Para implementar expresiones lambda, javac crea una instrucción INVOKEDYNAMIC que delega la invocación al método bootstrap de LambdaMetafactory . Para OpenJDK, este método de arranque está creando una implementación de la interfaz requerida en el tiempo de ejecución utilizando ASM .

Dentro del método de test , el tester instancia tester es de esta clase generada por ASM, por lo que no tiene que leer ningún archivo de clase para averiguar qué método representa el Predicate . En el caso general, la decisión exacta de cómo asignar las expresiones lambda a las implementaciones de interfaz se deja al entorno de ejecución, lo que hace que su problema sea aún más difícil. La única forma de averiguar qué método representa una expresión lambda es leer el código de byte de su creación, es decir, interpretar el método main , para su ejemplo.


Si solo quieres VER el bytecode:

javap -c -p -v classfile ^disassemble ^private methods ^verbose, including constant pool and bootstrap methods attribute

Pero si quieres intentar hacer esto en tiempo de ejecución, estás bastante desafortunado (por diseño, no tenemos nada como Árboles de Expresión), como sugiere la otra respuesta.