且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Lambda 表达式和变量捕获

更新时间:2023-11-11 12:27:34

首先我们可以看一下JLS,声明如下:

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

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

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.

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

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.

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

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.

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

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).


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


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

}

main 的输出是

Hello
Hello world!
Hello you there!

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

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 实例中.

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

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

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

是否应该将值复制到您的实例,但在局部变量更新时不更新?这可能会引起混淆,因为我认为许多程序员都希望 foo 具有对你们所有人"的新值.返回此 lambda 后.

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 foo to have the new value "to all of you" after returning this 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.