2015-08-28 60 views
5

请向我解释lambda表达式如何使用和修改它的封装类的实例变量,并且只能使用它的封闭范围的局部变量(除非它是final或有效的final)? 我的基本问题是类的实例变量中的λ是如何修改的,并且局部变量不是在范围Lambda表达式和变量捕获

+2

由于lambda实际上是一个匿名内部类,这是[为什么只有最终变量可以在匿名类中访问?](http://stackoverflow.com/questions/ 4732544/why-only-final-variables-accessible-in-anonymous-class) – mthmulders

+1

对于Java 8来说这是真实的,但据我所知它没有被指定,所以这可以在未来版本的Java中改变。 – Alex

+3

只有'this'被捕获。所有对lambda体内实例变量的访问都是通过'this.field'完成的,因此你也可以写入它。 – ZhongYu

回答

8

方面首先,我们可以看看在JLS,其中规定了以下内容:

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

任何使用但未在lambda体中声明的局部变量必须在lambda体之前明确赋值(§16(Definite Assignment)),否则会发生编译时错误。

有关变量使用的相似规则适用于内部类的主体(第8.1.3节)。对有效最终变量的限制禁止访问动态更改的局部变量,它们的捕获可能会引入并发问题。与最终的限制相比,它减少了程序员的文书负担。

对有效最终变量的限制包括标准循环变量,但不包括增强型for循环变量,循环变量在循环的每次迭代中都被视为不同(第14.14.2节)。


为了更好地理解它,看看这个例子类:

输出的
public class LambdaTest { 

    public static void main(String[] args) { 
     LambdaTest test = new LambdaTest(); 
     test.returnConsumer().accept("Hello"); 
     test.returnConsumerWithInstanceVariable().accept("Hello"); 
     test.returnConsumerWithLocalFinalVariable().accept("Hello"); 
    } 

    String string = " world!"; 

    Consumer<String> returnConsumer() { 
     return ((s) -> {System.out.println(s);}); 
    } 

    Consumer<String> returnConsumerWithInstanceVariable() { 
     return ((s) -> {System.out.println(s + string);}); 
    } 

    Consumer<String> returnConsumerWithLocalFinalVariable() { 
     final String foo = " you there!"; 
     return ((s) -> {System.out.println(s + foo);}); 
    } 

} 

主要是

Hello 
Hello world! 
Hello you there! 

这是因为这里返回拉姆达是多少与创建new Consumer<String>() {...}的新匿名类相同。你的lambda - 一个Consumer<String>的实例有一个对它创建的类的引用。你可以重写returnConsumerWithInstanceVariable来使用System.out.println(s + LambdaTest.this.string),这将完全相同。这就是允许你访问(和修改)实例变量的原因。

如果你的方法中有一个(有效的)最终局部变量,你可以访问它,因为你可以访问它,因为它被复制到你的lambda实例。

但是,但是,如果它不是终点,你觉得在以下方面应该发生:

Consumer<String> returnConsumerBad() { 
    String foo = " you there!"; 
    Consumer<String> results = ((s) -> {System.out.println(s + foo);}); 
    foo = " to all of you!"; 
    return results; 
} 

如果该值被复制到您的实例,但后来没有更新,当局部变量更新?这可能会引起混淆,因为我认为许多程序员会希望富美在返回此lambda后拥有“给所有人”的新价值。

如果你有一个原始值,它会放在堆栈上。所以你不能简单地引用局部变量,因为它可能会在方法结束后消失。

0

你可以参考这篇文章 - https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood关于lambda表达式编译的解释。正如所解释的lambda表达式/代码块被编译到匿名类中,这些匿名类将使用名称格式(<<Enclosing Class name>>$<<1(Number)>>)进行编译,因此假设假设允许非最终局部变量,那么编译器无法从引用该局部变量的位置追踪它因为匿名类的.class文件是用前述格式创建/编译的,就像普通的java类一样。

因此,如果局部变量是最终的,那么编译器会在不会给编译器造成歧义的类动态类中创建一个最终实例。 请参考上面的链接提供更多信息