Ada*_*zek 56
字段和局部变量之间的根本区别在于,当JVM创建lambda实例时,会复制局部变量.另一方面,字段可以自由更改,因为它们的更改也会传播到外部类实例(它们的范围是整个外部类,正如Boris在下面指出的那样).
关于匿名类,闭包和labmdas的最简单的思考方式是从变量范围的角度来看; 想象一下为传递给闭包的所有局部变量添加的复制构造函数.
Not*_*bug 22
在项目文件lambda:Lambda v4的状态
根据第7节,变量捕获,提到....
我们的意图是禁止捕获可变的局部变量.原因是这样的成语:
Run Code Online (Sandbox Code Playgroud)int sum = 0; list.forEach(e -> { sum += e.size(); });基本上是连续的; 写这样没有竞争条件的lambda体是很困难的.除非我们愿意强制执行 - 最好是在编译时 - 这样的函数无法逃避其捕获线程,否则此功能可能会导致比解决更多的麻烦.
编辑:
另外需要注意的是,当你在内部类中访问它们时,局部变量在内部类的构造函数中传递,而这对于非最终变量不起作用,因为非最终变量的值可以在构造之后更改.
在实例变量的情况下,编译器传递类的引用,类的引用将用于访问实例变量.因此,在实例变量的情况下不需要它.
PS:值得一提的是,匿名类只能访问最终的局部变量(在JAVA SE 7中),而在Java SE 8中,您可以有效地访问lambda内部的最终变量以及内部类.
new*_*cct 14
因为实例变量总是通过对某个对象的引用的字段访问操作来访问,即some_expression.instance_variable.即使您没有通过点表示法显式访问它,例如instance_variable,它也被隐含地视为this.instance_variable(或者如果您在内部类中访问外部类的实例变量,那么它就OuterClass.this.instance_variable在引擎盖下this.<hidden reference to outer this>.instance_variable).
因此,永远不会直接访问实例变量,而您直接访问的实际"变量" this(由于它不可分配,因此是"有效的最终"),或者是某个其他表达式开头的变量.
sed*_*ooe 14
在Java 8 in Action书中,这种情况解释为:
你可能会问自己为什么局部变量有这些限制.首先,在幕后实现实例和局部变量的方式存在关键差异.实例变量存储在堆上,而局部变量存在于堆栈中.如果lambda可以直接访问局部变量并且lambda在一个线程中使用,那么使用lambda的线程可能会在分配了该变量的线程解除分配后尝试访问该变量.因此,Java实现对自由局部变量的访问,作为对其副本的访问,而不是访问原始变量.如果局部变量仅被分配一次,则没有区别 - 因此限制.其次,这种限制也阻碍了典型的命令式编程模式(正如我们在后面的章节中所解释的那样,阻止了简单的并行化)来改变外部变量.
hag*_*wal 11
为未来的访客提出一些概念:
基本上这一切都归结为编译器应该能够确定地告诉lambda表达式主体不在变量的陈旧副本上工作.
在局部变量的情况下,编译器无法确定lambda表达式主体是否在变量的陈旧副本上工作,除非该变量是最终的或有效的最终变量,因此局部变量应该是最终的或有效的最终变量.
现在,在实例字段的情况下,当你访问lambda表达式中的实例字段时,编译器会将a附加this到该变量访问(如果你没有显式地完成)并且因为this它实际上是最终的,所以编译器确保lambda表达体将始终拥有变量的最新副本(请注意,此讨论现在多线程超出了范围).因此,在实例字段的情况下,编译器可以告诉lambda主体具有实例变量的最新副本,因此实例变量不需要是最终的或有效的最终.请参考下面的Oracle幻灯片截图:
此外,请注意,如果您正在访问lambda表达式中的实例字段并且正在多线程环境中执行,那么您可能会遇到问题.
看起来你问的是可以从lambda体引用的变量.
使用但未在lambda表达式中声明的任何局部变量,形式参数或异常参数必须声明为final或者是有效的final(§4.12.4),否则在尝试使用时会发生编译时错误.
所以你不需要声明变量,因为final你只需要确保它们是"有效的最终".这与适用于匿名类的规则相同.
在Lambda表达式中,您可以有效地使用周围范围内的最终变量.有效地意味着声明变量final不是强制性的,但要确保不要在lambda表达式中更改其状态.
你也可以在闭包中使用它,使用"this"表示封闭对象但不是lambda本身,因为闭包是匿名函数,它们没有与之关联的类.
所以当你使用任何字段(比如说私有的Integer i;)来自封闭的类,这个字段没有被声明为final而不是有效的最终时它仍然会起作用,因为编译器会代表你并插入"this"(this.i) .
private Integer i = 0;
public void process(){
Consumer<Integer> c = (i)-> System.out.println(++this.i);
c.accept(i);
}
Run Code Online (Sandbox Code Playgroud)
这是一个代码示例,正如我没想到的那样,我希望无法修改lambda之外的任何内容
public class LambdaNonFinalExample {
static boolean odd = false;
public static void main(String[] args) throws Exception {
//boolean odd = false; - If declared inside the method then I get the expected "Effectively Final" compile error
runLambda(() -> odd = true);
System.out.println("Odd=" + odd);
}
public static void runLambda(Callable c) throws Exception {
c.call();
}
}
Run Code Online (Sandbox Code Playgroud)
输出:奇数=真