简体   繁体   English

编译后的 lambda function 中的额外参数从何而来?

[英]Where does the extra parameter in a compiled lambda function come from?

I'm trying to figure out exactly how lambdas and closures work in the JVM. To that end, I've tried compiling this simple test case:我试图弄清楚 lambda 和闭包在 JVM 中是如何工作的。为此,我尝试编译这个简单的测试用例:

import java.util.function.*;

class Adder {
  static Function<Float, Float> makeAdder(Float a) {
    return b -> a + b;
  }

  public static void main(String[] args) {
    Function<Float, Float> f = makeAdder(1.23f);
    System.out.println(f.apply(4.56f));
  }
}

Disassembling the resulting byte code is interesting:反汇编生成的字节码很有趣:

  static java.util.function.Function<java.lang.Float, java.lang.Float> makeAdder(java.lang.Float);
    descriptor: (Ljava/lang/Float;)Ljava/util/function/Function;
    flags: (0x0008) ACC_STATIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #7,  0              // InvokeDynamic #0:apply:(Ljava/lang/Float;)Ljava/util/function/Function;
         6: areturn
      LineNumberTable:
        line 4: 0
    Signature: #48                          // (Ljava/lang/Float;)Ljava/util/function/Function<Ljava/lang/Float;Ljava/lang/Float;>;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: ldc           #11                 // float 1.23f
         2: invokestatic  #12                 // Method java/lang/Float.valueOf:(F)Ljava/lang/Float;
         5: invokestatic  #18                 // Method makeAdder:(Ljava/lang/Float;)Ljava/util/function/Function;
         8: astore_1
         9: getstatic     #23                 // Field java/lang/System.out:Ljava/io/PrintStream;
        12: aload_1
        13: ldc           #29                 // float 4.56f
        15: invokestatic  #12                 // Method java/lang/Float.valueOf:(F)Ljava/lang/Float;
        18: invokeinterface #30,  2           // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
        23: invokevirtual #35                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        26: return
      LineNumberTable:
        line 9: 0
        line 10: 9
        line 11: 26

  private static java.lang.Float lambda$makeAdder$0(java.lang.Float, java.lang.Float);
    descriptor: (Ljava/lang/Float;Ljava/lang/Float;)Ljava/lang/Float;
    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: invokevirtual #41                 // Method java/lang/Float.floatValue:()F
         4: aload_1
         5: invokevirtual #41                 // Method java/lang/Float.floatValue:()F
         8: fadd
         9: invokestatic  #12                 // Method java/lang/Float.valueOf:(F)Ljava/lang/Float;
        12: areturn
      LineNumberTable:
        line 4: 0
        

Some of the above is clear, some less so.上面的一些很清楚,有些则不太清楚。 The part I'm most puzzled about right now is the implementation of the lambda function, lambda$makeAdder$0(java.lang.Float, java.lang.Float) .我现在最困惑的部分是 lambda function, lambda$makeAdder$0(java.lang.Float, java.lang.Float)的实现。 That signature suggests the lambda has two parameters even though it was declared with just one.该签名表明 lambda 有两个参数,即使它只用一个参数声明。

Well, it's obvious what the extra one is for;好吧,很明显额外的一个是做什么用的; it's for the value of a that was bound into the closure.它用于绑定到闭包中的a的值。 So at one level, that answers the question of how Java closures are supplied with the values of bound variables: they are prepended to the parameter list.因此,在一个层面上,这回答了 Java 闭包如何提供绑定变量值的问题:它们被添加到参数列表中。

But then, how does the ultimate caller know about this?但是,最终调用者如何知道这一点? The disassembled code for main looks isomorphic to the source code, ie completely innocent of knowledge about how closures are implemented. main的反汇编代码看起来与源代码同构,即完全不知道闭包是如何实现的。 It seems to be supplying one argument to makeAdder , then the second argument to the lambda. In other words, supplying just one argument to the lambda.它似乎向 makeAdder 提供一个参数,然后向makeAdder提供第二个参数。换句话说,只向 lambda 提供一个参数。

How is the first argument also supplied to the lambda?第一个参数如何也提供给 lambda?

Does it anything to do with the final BootstrapMethods section of the disassembled code?它与反汇编代码的最终BootstrapMethods部分有什么关系吗?

BootstrapMethods:
  0: #56 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:
      #63 (Ljava/lang/Object;)Ljava/lang/Object;
      #64 REF_invokeStatic Adder.lambda$makeAdder$0:(Ljava/lang/Float;Ljava/lang/Float;)Ljava/lang/Float;
      #67 (Ljava/lang/Float;)Ljava/lang/Float;
InnerClasses:
  public static final #74= #70 of #72;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

The relevant aspect is in the output of javap but you didn't include it in your question.相关方面在javap的 output 中,但您没有将其包含在您的问题中。 At the very end of the javap output: (make sure to run with javap -c -v !)在 javap output 的最后:(确保使用javap -c -v运行!)

SourceFile: "Test.java"
BootstrapMethods:
  0: #56 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:
      #63 (Ljava/lang/Object;)Ljava/lang/Object;
      #64 REF_invokeStatic Adder.lambda$makeAdder$0:(Ljava/lang/Float;Ljava/lang/Float;)Ljava/lang/Float;
      #67 (Ljava/lang/Float;)Ljava/lang/Float;
InnerClasses:
  public static final #74= #70 of #72;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

This... doesn't really come across as useful, but it is the underlying mechanism that makes all this work.这......并没有真正发挥作用,但它使所有这些工作起作用的底层机制。

The relevant call is the invokedynamic bytecode instruction.相关调用是invokedynamic字节码指令。 invokedynamic is, well, dynamic: The first time invokedynamic is encountered the JVM will take a slow path and executes some code that can arbitrarily decide what this invocation is actually going to end up looking like. invokedynamic是动态的:第一次遇到invokedynamic时,JVM 将采用慢速路径并执行一些代码,这些代码可以任意决定此调用实际上最终会是什么样子。 Any further executions of that same invokedynamic call no longer do that - they 'memoize'.同一个invokedynamic调用的任何进一步执行都不再这样做 - 它们“记忆”。 So, an invokedynamic call can result in any kind of actual method invocation you want (here, it'll end up being a partial application of that lambda$makeAdder$0(float, float) , of course), and is nevertheless fast.因此, invokedynamic调用可以导致您想要的任何类型的实际方法调用(当然,在这里,它最终将成为lambda$makeAdder$0(float, float)部分应用程序),而且速度仍然很快。

javap 's output isn't all that enlightening. javap的 output 并不是那么有启发性。 A lot of the crucial moving parts of it all are actually in the java.lang.invoke.LambdaMetaFactory class which is a real class in the JVM whose source is part of the core JDK and thus open .它的许多关键移动部分实际上都在java.lang.invoke.LambdaMetaFactory class 中,它是 JVM 中真正的 class, 其源是核心 JDK 的一部分,因此是开放的

This baeldung tutorial on invokedynamic is probably something you want to go through top to bottom if you are interested in fully understanding how this works.如果您有兴趣完全了解它的工作原理,那么这个关于invokedynamic的 baeldung 教程可能是您想要从上到下 go 的东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM