java Lambda 表达式和变量捕获

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32272713/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 19:56:03  来源:igfitidea点击:

Lambda Expression and Variable Capture

javavariableslambdascopeexpression

提问by Abu Bakkar Siddique

Please explain to me how a lambda expression can use and modify instance variables of its enclosing class, but can only use local variables of its enclosing scope. (Unless it is final or effective final?)

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

My basic question is how instance variables of a class are modifiable from within a lambda and local variables are not, in the context of scope.

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

回答by Alex

At first, we can take a look at the JLS, which states the following:

首先,我们可以看看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).

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

任何在 lambda 主体中使用但未声明的局部变量都必须在 lambda 主体之前明确分配(第 16 节(确定分配)),否则会发生编译时错误。

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

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



To understand it better, take a look at this example class:

为了更好地理解它,请看一下这个示例类:

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);});
    }

}

Output of main is

main 的输出是

Hello
Hello world!
Hello you there!

This is because returning a lambda here is much the same as creating a new anonymous class with new Consumer<String>() {...}. Your lambda - an instance of Consumer<String>has a reference to the class it has been created in. You could rewrite the returnConsumerWithInstanceVariable()to use System.out.println(s + LambdaTest.this.string), this would do exactly the same. This is why you are allowed to to access (and modify) instance variables.

这是因为在此处返回 lambda 与使用new Consumer<String>() {...}. 您的 lambda - 的实例Consumer<String>具有对它在其中创建的类的引用。您可以重写returnConsumerWithInstanceVariable()to use System.out.println(s + LambdaTest.this.string),这将完全相同。这就是为什么允许您访问(和修改)实例变量的原因。

If you have a (effectively) final local variable in your method, you can access it because it gets copied to your lambda instance.

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

But, however, if its not final, what do you think should happen in the following context:

但是,如果它不是最终的,您认为在以下情况下会发生什么:

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

Should the value get copied to your instance, but then not get updated when the local variable is updated? That would probably cause confusion, as I think many programmers would expected footo have the new value "to all of you" after returning this lambda.

该值是否应该被复制到您的实例,但在局部变量更新时不更新?这可能会引起混淆,因为我认为许多程序员都希望foo在返回这个 lambda 之后“给你们所有人”拥有新值。

If you had a primitive value, it would lay on the stack. So you could not simply reference the local variable, because it could disappear after the end of your method is reached.

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

回答by C T Mithun

You can refer this article - https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hoodexplained on lambda expressions compilation. As explained lambda expressions/blocks of code are compiled into the anonymous class, these anonymous class are compiled with the name format (<<Enclosing Class name>>$<<1(Number)>>), so suppose assume if non final local variables are allowed then compiler cannot trace it from where this local variable is referred as anonymous class' '.class' files are created/compiled with aforementioned format separately like normal java classes.

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

So if local variable is final then compiler creates a final instance in the anoymous class which doesn't create ambiguity to compiler. Refer the link mention above for more information

因此,如果局部变量是最终的,那么编译器会在匿名类中创建一个最终实例,该实例不会对编译器造成歧义。有关更多信息,请参阅上面提到的链接