英文:
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)
。
-
使用实例方法:
private void compiler$chosen$name(String s) { System.out.println(this); }
-
使用
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)
.
-
use an instance method:
private void compiler$chosen$name(String s) { System.out.println(this); }
-
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论