Где ссылка на лямбда-функцию? - PullRequest
3 голосов
/ 27 января 2020

Я пытаюсь понять, как именно работают лямбды и функции высшего порядка на уровне JVM в современном Java. Я написал этот простой тестовый класс:

public final class Main {
    public static void main(String[] args) {
        var s = new Object[] { 1.0, 2.0, 3.0 };
        System.out.println(sum(s, x -> 1000000.0));
    }

    public static double sum(Object[] s, Function<Object, Double> f) {
        var r = 0.0;
        for (var a : s) {
            r += f.apply(a);
        }
        return r;
    }
}

, который компилируется в это:

Compiled from "Main.java"
public final class prover.Main {
  public prover.Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static double sum(java.lang.Object[], java.util.function.Function<java.lang.Object, java.lang.Double>);
    Code:
       0: dconst_0
       1: dstore_2
       2: aload_0
       3: astore        4
       5: aload         4
       7: arraylength
       8: istore        5
      10: iconst_0
      11: istore        6
      13: iload         6
      15: iload         5
      17: if_icmpge     50
      20: aload         4
      22: iload         6
      24: aaload
      25: astore        7
      27: dload_2
      28: aload_1
      29: aload         7
      31: invokeinterface #7,  2            // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
      36: checkcast     #13                 // class java/lang/Double
      39: invokevirtual #15                 // Method java/lang/Double.doubleValue:()D
      42: dadd
      43: dstore_2
      44: iinc          6, 1
      47: goto          13
      50: dload_2
      51: dreturn

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: anewarray     #2                  // class java/lang/Object
       4: dup
       5: iconst_0
       6: dconst_1
       7: invokestatic  #19                 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
      10: aastore
      11: dup
      12: iconst_1
      13: ldc2_w        #23                 // double 2.0d
      16: invokestatic  #19                 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
      19: aastore
      20: dup
      21: iconst_2
      22: ldc2_w        #25                 // double 3.0d
      25: invokestatic  #19                 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
      28: aastore
      29: astore_1
      30: getstatic     #27                 // Field java/lang/System.out:Ljava/io/PrintStream;
      33: aload_1
      34: invokedynamic #33,  0             // InvokeDynamic #0:apply:()Ljava/util/function/Function;
      39: invokestatic  #36                 // Method sum:([Ljava/lang/Object;Ljava/util/function/Function;)D
      42: invokevirtual #42                 // Method java/io/PrintStream.println:(D)V
      45: return

  private static java.lang.Double lambda$main$0(java.lang.Object);
    Code:
       0: ldc2_w        #48                 // double 1000000.0d
       3: invokestatic  #19                 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
       6: areturn
}

Теперь сама лямбда-функция компилируется в закрытый метод stati c в конце, это достаточно ясно. Но где это упоминается? Код для вызова sum выглядит следующим образом:

      33: aload_1
      34: invokedynamic #33,  0             // InvokeDynamic #0:apply:()Ljava/util/function/Function;
      39: invokestatic  #36                 // Method sum:([Ljava/lang/Object;Ljava/util/function/Function;)D

Это как-то относится к лямбда-функции? Если так, то как? Какая ссылка?

1 Ответ

5 голосов
/ 27 января 2020

Использование javap -p -v приведет к созданию раздела, помеченного BootstrapMethods, в котором перечислены все bootstrap методы, используемые для инициализации лямбда-выражений:

BootstrapMethods:
  0: #41 REF_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:
      #42 (Ljava/lang/Object;)Ljava/lang/Object;
      #43 REF_invokeStatic Scratch.lambda$main$0:(Ljava/lang/Object;)Ljava/lang/Double;
      #44 (Ljava/lang/Object;)Ljava/lang/Double;

В этом разделе содержатся ссылки на методы, реализующие фактический код (Scratch.lambda$main$0 в моем случае точное имя будет меняться в зависимости от компилятора-vendor / -version / -flags).

Обратите внимание, что представление в файлах Class намеренно сохранено на довольно высоком уровне (есть bootstrap методы, которые возвращают фактический код, который будет выполнен во время выполнения). Это означает, что JVM не имеет много ограничений относительно того, как реализовать и оптимизировать это. Это также означает, что изучение байт-кода скажет вам только многое, потому что JVM может довольно свободно интерпретировать то, что он там видит.

...