Java Lambdas:局部变量需要最终实例变量don';T
在lambda中,局部变量需要是最终变量,但实例变量不需要。为什么会这样?您似乎在询问可以从lambda体引用的变量 从 在lambda表达式中使用但未声明的任何局部变量、形式参数或异常参数必须声明为final或有效final(§4.12.4),否则在尝试使用时会发生编译时错误Java Lambdas:局部变量需要最终实例变量don';T,java,lambda,java-8,final,Java,Lambda,Java 8,Final,在lambda中,局部变量需要是最终变量,但实例变量不需要。为什么会这样?您似乎在询问可以从lambda体引用的变量 从 在lambda表达式中使用但未声明的任何局部变量、形式参数或异常参数必须声明为final或有效final(§4.12.4),否则在尝试使用时会发生编译时错误 因此,您不需要将变量声明为final,只需要确保它们是“有效的final”。这与适用于匿名类的规则相同。字段和局部变量之间的根本区别在于,当JVM创建lambda实例时,局部变量被复制。另一方面,字段可以自由更改,因为对
因此,您不需要将变量声明为
final
,只需要确保它们是“有效的final”。这与适用于匿名类的规则相同。字段和局部变量之间的根本区别在于,当JVM创建lambda实例时,局部变量被复制。另一方面,字段可以自由更改,因为对字段的更改也会传播到外部类实例(它们的范围是整个外部类,正如Boris在下面指出的)
考虑匿名类、闭包和labmda的最简单方法是从变量范围的角度;假设为传递给闭包的所有局部变量添加了一个复制构造函数。在项目lambda的文档中: 在第7节下。变量捕获,提到 我们的目的是禁止捕获可变的局部变量。这个 原因是这样的成语:
int sum = 0;
list.forEach(e -> { sum += e.size(); });
基本上是连续的;写lambda体是相当困难的
像这样没有比赛条件。除非我们愿意
最好在编译时强制这样的函数不能转义
它的捕获线程,这个特性可能会比它带来更多的麻烦
解决
编辑:
这里要注意的另一件事是,当您在内部类中访问局部变量时,它们会在内部类的构造函数中传递,而这对非最终变量不起作用,因为非最终变量的值在构造之后可以更改
而对于实例变量,编译器传递类的引用,类的引用将用于访问实例变量。因此,在实例变量的情况下不需要它
PS:值得一提的是,匿名类只能访问最终的局部变量(在JAVA SE 7中),而在JAVA SE 8中,您可以有效地访问lambda内部以及内部类中的最终变量。因为实例变量总是通过对某个对象的引用的字段访问操作来访问,例如,
一些表达式。实例变量
。即使您没有通过点符号显式地访问它,比如实例变量
,它也被隐式地视为这个.instance变量
(或者,如果您在一个内部类中访问一个外部类的实例变量,OuterClass.this.instance\u variable
,它位于引擎盖下this..instance\u variable
)
因此,实例变量永远不会被直接访问,而您直接访问的真正“变量”是
this
(这是“有效的最终变量”,因为它是不可赋值的),或其他表达式开头的变量。这里是一个代码示例,因为我也没有预料到这一点,所以我预计无法修改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();
}
}
输出:
Odd=true在Lambda表达式中,可以有效地使用周围范围中的最终变量。 实际上,这意味着不必声明变量final,但要确保不在lambda表达式中更改其状态 您也可以在闭包中使用它,使用“this”表示封闭对象,而不是lambda本身,因为闭包是匿名函数,并且它们没有与之关联的类 因此,当您使用封闭类中未声明为final且未有效声明为final的任何字段(比如private Integer i;)时,它仍将起作用,因为编译器会代表您使用技巧并插入“this”(this.i)
私有整数i=0;
公共程序(){
消费者c=(i)->System.out.println(++this.i);
c、 接受(i);
}
在本书中,这种情况被解释为:
您可能会问自己为什么局部变量有这些限制。
首先,有一把钥匙
实例和局部变量在幕后实现方式上的差异。实例
变量存储在堆上,而局部变量位于堆栈上
直接访问局部变量,在线程中使用lambda,然后线程使用
lambda可以在分配变量的线程停止后尝试访问该变量
因此,Java实现了对自由局部变量的访问,作为对其副本的访问
而不是访问原始变量。如果局部变量是
仅分配给一次,因此受到限制。
第二,这种限制也不鼓励典型的命令式编程模式(正如我们所说
在后面的章节中解释,防止容易的并行化)来变异外部变量
是的,您可以更改实例的成员变量,但是不能像处理变量一样更改实例本身 如前所述:
class Car {
public String name;
}
public void testLocal() {
int theLocal = 6;
Car bmw = new Car();
bmw.name = "BMW";
Stream.iterate(0, i -> i + 2).limit(2)
.forEach(i -> {
// bmw = new Car(); // LINE - 1;
bmw.name = "BMW NEW"; // LINE - 2;
System.out.println("Testing local variables: " + (theLocal + i));
});
// have to comment this to ensure it's `effectively final`;
// theLocal = 2;
}
限制局部变量的基本原则是
如果第二个线程评估的lambda具有变异局部变量的能力。即使能够从不同线程读取可变局部变量的值,也会引入同步的必要性,或者使用volatile,以避免读取陈旧数据
但正如我们所知
在各种各样的原因中,Java平台最紧迫的一个原因是它们
class Car {
public String name;
}
public void testLocal() {
int theLocal = 6;
Car bmw = new Car();
bmw.name = "BMW";
Stream.iterate(0, i -> i + 2).limit(2)
.forEach(i -> {
// bmw = new Car(); // LINE - 1;
bmw.name = "BMW NEW"; // LINE - 2;
System.out.println("Testing local variables: " + (theLocal + i));
});
// have to comment this to ensure it's `effectively final`;
// theLocal = 2;
}