Можете ли вы проверить байтовый код Java 8 лямбда во время выполнения?
Если у вас есть анонимный класс, например
Predicate<String> isEmpty = new Predicate<String>() {
public boolean test(String t) {
return t.isEmpty();
}
};
Библиотека, передающая ссылку на isEmpty
, может проверять байтовый код, чтобы видеть, что он делает и, возможно, манипулировать им. Есть ли способ сделать это для лямбда?
Predicate<String> isEmpty = String::isEmpty;
Например, скажем, этот код и байтовый код
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
}
С ссылкой на tester
в test
, как мне найти, какой метод вызывается?
Ответы
Ответ 1
Если вы просто хотите посмотреть байт-код:
javap -c -p -v classfile
^disassemble
^private methods
^verbose, including constant pool and bootstrap methods attribute
Но если вы хотите попробовать это во время выполнения, вам не повезло (по дизайну, у нас нет ничего подобного деревьям выражений), как показывает другой ответ.
Ответ 2
Простой ответ: вы не можете. В этом вопросе есть ответ Брайана Гетца. Для реализации лямбда-выражений javac создает инструкцию INVOKEDYNAMIC
, которая делегирует вызов LambdaMetafactory
метод начальной загрузки. Для OpenJDK этот метод начальной загрузки затем создает реализацию требуемого интерфейса во время выполнения, используя ASM.
В рамках метода test
возвращаемый экземпляр tester
относится к этому классу, создаваемому ASM, так что у вас нет файла класса для чтения, чтобы узнать, какой метод представляет Predicate
. В общем случае точное решение о том, как сопоставить лямбда-выражения с реализациями интерфейса, остается в среде выполнения, что еще более усложняет вашу проблему. Единственный способ узнать, какой метод представляет собой лямбда-выражение, - это прочитать байтовый код его создания, т.е. Интерпретировать метод main
для вашего примера.
Ответ 3
В конце концов, я нашел способ получить файл класса выражения лямбда, который является довольно надежным (но, конечно, все еще зависит от деталей реализации). Для экспериментальной реализации я теперь использую Java-агент, который позволяет ретрансформировать классы, но сам реализуется как нет-op, меня интересует только двоичный массив, который передается в качестве аргумента.
После получения экземпляра Instrumentation
через зарегистрированный Java-агент один регистрирует a ClassFileTransformer
, который затем уведомляется о двоичном представлении после повторной передачи lambdaInstance.getClass()
. Это, конечно, довольно хакерское решение и может сломаться после того, как внутренняя реализация lambdas будет изменена в будущем.
К сожалению, я не нашел никакой документации о том, как агент должен вести себя с ретрансформацией класса синтетического класса лямбда-выражения на совместимой со стандартами JVM.
Ответ 4
Я нашел другой способ: исправляя InnerClassLambdaMetafactory
, можно добавить аннотацию к лямбда-классам, которые указывают на метод реализации. Это возможно во время выполнения загрузки агента и повторной передачи метафабрики. Подробнее см. мой блог.