当我将 lambda 表达式作为参数传递时,它怎么可能访问此范围内的其他变量?
How is it possible that when I pass a lambda expression as a parameter it can access other variables in this scope?
public class ArraysDemo {
public static void main(String[] args) {
int[] a = {0, 2, 4, 6, 8};
int[] b = {10, 12, 14, 16, 18};
Arrays.setAll(a, i -> b[i]+1);
System.out.println(Arrays.toString(a));
}
}
输出:[11, 13, 15, 17, 19]
使用的setAll()
函数的来源如下:
public static void setAll(int[] array, IntUnaryOperator generator) {
Objects.requireNonNull(generator);
for (int i = 0; i < array.length; i++)
array[i] = generator.applyAsInt(i);
}
IntUnaryOperator
是一个功能接口,这是其来源的一部分:
public interface IntUnaryOperator {
int applyAsInt(int operand);
// rest methods are omitted
}
如果我错了请纠正我,但我对 Java 中的 lambda 表达式的理解是,当我将 lambda 表达式作为参数传递给 setAll()
方法时,匿名 class 实现了 IntUnaryOperator
接口,创建并调用 generator
。而 lambda 表达式本质上是 applyAsInt()
方法的一个实现,所以我相信它会转化为类似这样的东西:
int applyAsInt(int operand){
return b[operand]+1;
}
它可以访问 operand
对我来说很有意义,因为它作为参数传递给 array[i] = generator.applyAsInt(i);
但是,我不明白它如何操纵 b
- 它没有作为参数传递,那么它怎么可能被引用呢?我错过了什么?
原因是 b
有效最终。
Instance and static variables may be used and changed without
restriction in the body of a lambda. The use of local variables,
however, is more restricted: capture of local variables is not allowed
unless they are effectively final, a concept introduced in Java 8.
Informally, a local variable is effectively final if its initial value
is never changed (including within the body of a lambda expression)—in
other words, declaring it final would not cause a compilation failure.
The concept of effective finality does not introduce any new semantics
to Java; it is simply a slightly less verbose way of defining final
variables.
来自http://www.lambdafaq.org/can-lambda-expressions-use-variables-from-their-environment/
Arrays.setAll(a, i -> b[i]+1);
相当于效果:
Arrays.setAll(a, new IntUnaryOperator() {
private int[] b$ = b;
@Override
public int applyAsInt(int i);
return b$[i]+1;
}
});
那是一个与原来b
同名同值的变量b
会被保留。需要此副本,因为原始变量(或新变量)的寿命可能比另一个更有限。例如作为局部变量。
为了隐藏现在有两个变量,要求不再将它们分配给 ("effectively final"),这会导致 "same" 变量的值不同。
即使没有 lambda 表达式,例如使用匿名内部 classes,您可以 捕获 周围上下文的值,即
int[] a = {0, 2, 4, 6, 8};
int[] b = {10, 12, 14, 16, 18};
Arrays.setAll(a, new IntUnaryOperator() {
public int applyAsInt(int i) {
return b[i]+1;
}
});
但也有一些差异,虽然与您的问题无关。当使用匿名内部 class 时,关键字 this
和 super
引用内部 class 的实例,而在 lambda 表达式中,它们的含义与周围环境。此外,当内部 class 访问周围 class 的 private
成员时,编译器将插入 helper 方法来执行访问,而 lambda 表达式可以访问包含 [=45] 的成员=]自然。
为了实现这一点,您的 lambda 表达式 i -> b[i]+1
的代码将被编译为 您的 class 的合成方法,其形式为:
private static int lambda$main[=11=](int[] b, int i) {
return b[i]+1;
}
因为它是您 class 中的一个方法,它可以访问所有成员。
所以运行时生成的class相当于
final class ArraysDemo$Lambda implements IntUnaryOperator {
final int[] b;
ArraysDemo$Lambda(int[] b) {
this.b = b;
}
public int applyAsInt(int i) {
return ArraysDemo.lambda$main[=12=](b, i);
}
}
把重点放在等价这个词上,并不是说一定要完全这样(更何况,普通的Javaclasses 无法访问 private
方法 ArraysDemo.lambda$main[=18=]
).
但是这些只是技术细节,最重要的关键点是lambda表达式可以访问有效的final局部变量以及它们包含的成员class和编译器和运行时环境将确保它工作。您可以简单地认为它们 函数 在定义它们的上下文中被评估。
public class ArraysDemo {
public static void main(String[] args) {
int[] a = {0, 2, 4, 6, 8};
int[] b = {10, 12, 14, 16, 18};
Arrays.setAll(a, i -> b[i]+1);
System.out.println(Arrays.toString(a));
}
}
输出:[11, 13, 15, 17, 19]
使用的setAll()
函数的来源如下:
public static void setAll(int[] array, IntUnaryOperator generator) {
Objects.requireNonNull(generator);
for (int i = 0; i < array.length; i++)
array[i] = generator.applyAsInt(i);
}
IntUnaryOperator
是一个功能接口,这是其来源的一部分:
public interface IntUnaryOperator {
int applyAsInt(int operand);
// rest methods are omitted
}
如果我错了请纠正我,但我对 Java 中的 lambda 表达式的理解是,当我将 lambda 表达式作为参数传递给 setAll()
方法时,匿名 class 实现了 IntUnaryOperator
接口,创建并调用 generator
。而 lambda 表达式本质上是 applyAsInt()
方法的一个实现,所以我相信它会转化为类似这样的东西:
int applyAsInt(int operand){
return b[operand]+1;
}
它可以访问 operand
对我来说很有意义,因为它作为参数传递给 array[i] = generator.applyAsInt(i);
但是,我不明白它如何操纵 b
- 它没有作为参数传递,那么它怎么可能被引用呢?我错过了什么?
原因是 b
有效最终。
Instance and static variables may be used and changed without restriction in the body of a lambda. The use of local variables, however, is more restricted: capture of local variables is not allowed unless they are effectively final, a concept introduced in Java 8. Informally, a local variable is effectively final if its initial value is never changed (including within the body of a lambda expression)—in other words, declaring it final would not cause a compilation failure. The concept of effective finality does not introduce any new semantics to Java; it is simply a slightly less verbose way of defining final variables.
来自http://www.lambdafaq.org/can-lambda-expressions-use-variables-from-their-environment/
Arrays.setAll(a, i -> b[i]+1);
相当于效果:
Arrays.setAll(a, new IntUnaryOperator() {
private int[] b$ = b;
@Override
public int applyAsInt(int i);
return b$[i]+1;
}
});
那是一个与原来b
同名同值的变量b
会被保留。需要此副本,因为原始变量(或新变量)的寿命可能比另一个更有限。例如作为局部变量。
为了隐藏现在有两个变量,要求不再将它们分配给 ("effectively final"),这会导致 "same" 变量的值不同。
即使没有 lambda 表达式,例如使用匿名内部 classes,您可以 捕获 周围上下文的值,即
int[] a = {0, 2, 4, 6, 8};
int[] b = {10, 12, 14, 16, 18};
Arrays.setAll(a, new IntUnaryOperator() {
public int applyAsInt(int i) {
return b[i]+1;
}
});
但也有一些差异,虽然与您的问题无关。当使用匿名内部 class 时,关键字 this
和 super
引用内部 class 的实例,而在 lambda 表达式中,它们的含义与周围环境。此外,当内部 class 访问周围 class 的 private
成员时,编译器将插入 helper 方法来执行访问,而 lambda 表达式可以访问包含 [=45] 的成员=]自然。
为了实现这一点,您的 lambda 表达式 i -> b[i]+1
的代码将被编译为 您的 class 的合成方法,其形式为:
private static int lambda$main[=11=](int[] b, int i) {
return b[i]+1;
}
因为它是您 class 中的一个方法,它可以访问所有成员。
所以运行时生成的class相当于
final class ArraysDemo$Lambda implements IntUnaryOperator {
final int[] b;
ArraysDemo$Lambda(int[] b) {
this.b = b;
}
public int applyAsInt(int i) {
return ArraysDemo.lambda$main[=12=](b, i);
}
}
把重点放在等价这个词上,并不是说一定要完全这样(更何况,普通的Javaclasses 无法访问 private
方法 ArraysDemo.lambda$main[=18=]
).
但是这些只是技术细节,最重要的关键点是lambda表达式可以访问有效的final局部变量以及它们包含的成员class和编译器和运行时环境将确保它工作。您可以简单地认为它们 函数 在定义它们的上下文中被评估。