Java Lambdas:局部变量需要最终实例变量don';T

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实例时,局部变量被复制。另一方面,字段可以自由更改,因为对

在lambda中,局部变量需要是最终变量,但实例变量不需要。为什么会这样?

您似乎在询问可以从lambda体引用的变量

在lambda表达式中使用但未声明的任何局部变量、形式参数或异常参数必须声明为final或有效final(§4.12.4),否则在尝试使用时会发生编译时错误


因此,您不需要将变量声明为
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; 
    }