java - Lambda 表达式和变量捕获

标签 java variables lambda scope expression

请向我解释 lambda 表达式如何使用和修改其封闭类的实例变量,但只能使用其封闭范围的局部变量。 (除非是final还是effective final?)

我的基本问题是,在范围的上下文中,类的实例变量如何从 lambda 中修改,而局部变量则不能。

最佳答案

首先,我们可以看一下 JLS ,其中说明如下:

Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted.

Any local variable used but not declared in a lambda body must be definitely assigned (§16 (Definite Assignment)) before the lambda body, or a compile-time error occurs.

Similar rules on variable use apply in the body of an inner class (§8.1.3). The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems. Compared to the final restriction, it reduces the clerical burden on programmers.

The restriction to effectively final variables includes standard loop variables, but not enhanced-for loop variables, which are treated as distinct for each iteration of the loop (§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);});
    }

}

main 的输出是

Hello
Hello world!
Hello you there!

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

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

但是,然而,如果它不是最终的,你认为在以下情况下应该发生什么:

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

是否应该将该值复制到您的实例,但在更新局部变量时不更新?这可能会引起混淆,正如我认为许多程序员所期望的那样 foo返回此 lambda 后,“对你们所有人”都有新的值(value)。

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

关于java - Lambda 表达式和变量捕获,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32272713/

相关文章:

java - 工作递归巴比伦平方根,需要合并一个错误。

java - 继续出现此错误;线程中出现异常 "main"java.lang.NullPointerException

excel - 在VBA函数中计算二变量函数的值

c# - 字符串表达式到 c# 函数委托(delegate)

java - 将变量更改出 lambda java 中的 lambda 表达式范围

java - 处理一个类加载器

java - 如何将 Control-Parm 中的工作流属性传递到 Alfresco 中的 FTL 文件?

variables - 带有捕获变量的 Lambda

matlab - 具有可变限制的积分

java - 无法解析符号 'v' 和预期的 ) 和 ;