Подтвердить что ты не робот

Можете ли вы проверить байтовый код 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, как мне найти, какой метод вызывается?

4b9b3361

Ответ 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, можно добавить аннотацию к лямбда-классам, которые указывают на метод реализации. Это возможно во время выполнения загрузки агента и повторной передачи метафабрики. Подробнее см. мой блог.