“Invokedynamic”在非静态上下文中的使用

huangapple go评论72阅读模式
英文:

Invokedynamic with non-static context

问题

我了解到字节码的invokedynamic指令调用了lambda的static方法表示。

如果以上内容不正确,请告知。

如果正确,那么下面的代码是如何工作的?

String[] arr = new String[1];
Stream.of(arr).forEach((s) -> System.out.println(this));
英文:

I learnt that invokedynamic instruction of bytecode calls a static method representation of lambda.

Please let me know if that is incorrect.

if correct, then how is below code working?

String[] arr = new String[1];
Stream.of(arr).forEach((s) -> System.out.println(this));

答案1

得分: 4

这种说法并不正确,不能总是说 lambda 表达式会被编译成 static 方法。规范并没有指定它们的编译方式,这为捕获 this 的 lambda 表达式留下了两种不同的策略的可能性,比如你的例子 s -> System.out.println(this)

  1. 使用实例方法:

    private void compiler$chosen$name(String s) {
        System.out.println(this);
    }
    
  2. 使用 static 方法:

    private static void compiler$chosen$name(TypeOfThis var0, String s) {
        System.out.println(var0);
    }
    

无论是哪种方法,当 invokedynamic 指令指向 LambdaMetafactory 中的引导方法时,都能同样有效。在任一情况下,invokedynamic 指令都将具有一个签名,消耗一个 TypeOfThis 实例并产生一个 Consumer<String>。从 LambdaMetafactory 的文档中,你可以推断出它会将非static目标方法的接收者视为隐含的第一个参数,从而使得这两种变体的函数签名相同。唯一重要的是,作为 consumer 的 accept 方法的参数必须与参数列表中的最后一个参数对应。

在实践中,我遇到过这两种策略,所以这确实取决于编译器。

需要注意的是,当使用方法引用时,这些策略在源代码级别同样适用:

public class Example {
    BiConsumer<Example,String> variant1 = Example::instanceMethod;
    BiConsumer<Example,String> variant2 = Example::staticMethod;

    private void instanceMethod(String s) {
        System.out.println(this);
    }

    private static void staticMethod(Example instance, String s) {
        System.out.println(instance);
    }
}

这展示了方法接收者与 static 方法的第一个参数的等效性。然而,当涉及绑定参数时,只有 Consumer<String> c = this::instanceMethod; 能在方法引用中起作用。LambdaMetafactory 的其他绑定特性仅被编译器生成的 lambda 表达式的代码使用。

英文:

It’s not correct to say that lambda expressions were always compiled to a static method. It’s not specified, how they are compiled, which leaves room for two different strategies for a lambda expression that captures this, like your s -> System.out.println(this).

  1. use an instance method:

    private void compiler$chosen$name(String s) {
        System.out.println(this);
    }
    
  2. use a static method:

    private static void compiler$chosen$name(TypeOfThis var0, String s) {
        System.out.println(var0);
    }
    

Both methods work equally well when the invokedynamic instruction points to a bootstrap method in the LambdaMetafactory. In either case, the invokedynamic instruction will have a signature consuming a TypeOfThis instance and producing a Consumer<String>. From the documentation of the LambdaMetafactory, you can derive that it will treat the receiver of a non-static target method like an implied the first argument, which makes the functional signature of both variants identical. All that matters, is that the argument to the consumer’s accept method has to correspond to the last argument of the list.

I’ve encountered both strategies in practice, so this is indeed compiler dependent.

Note that these strategies also work on source code level, when using method references:

public class Example {
    BiConsumer<Example,String> variant1 = Example::instanceMethod;
    BiConsumer<Example,String> variant2 = Example::staticMethod;

    private void instanceMethod(String s) {
        System.out.println(this);
    }

    private static void staticMethod(Example instance, String s) {
        System.out.println(instance);
    }
}

This demonstrates the equivalence of a method receiver and the the first argument to a static method. However, when it comes to binding an argument, only Consumer<String> c = this::instanceMethod; works with method references. The other binding features of the LambdaMetafactory are only used by compiler generated code for lambda expressions.

huangapple
  • 本文由 发表于 2020年8月28日 13:01:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/63627720.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定