Java Lambda捕获实例变量

Java Lambda捕获实例变量,java,lambda,Java,Lambda,在令人沮丧的调试会话之后阅读JLS,我发现lambdas将有效地捕获最终局部变量的值,但如果引用实例变量,它将捕获对该变量的引用,这对多线程代码有严重影响 例如,以下是从更大的程序中提取的MCVE: public class LambdaCapture { public static void main(String[] args) throws Exception { Launcher i1 = new Launcher(); i1.launch

在令人沮丧的调试会话之后阅读JLS,我发现lambdas将有效地捕获最终局部变量的值,但如果引用实例变量,它将捕获对该变量的引用,这对多线程代码有严重影响

例如,以下是从更大的程序中提取的MCVE:

public class LambdaCapture
{
    public static void main(String[] args) throws Exception
    {
        Launcher i1 = new Launcher();
        i1.launchAsynchTask();
    }

    public static class Launcher
    {
        private int value = 10;

        public void launchAsynchTask() throws Exception
        {
            System.out.printf("In launchAsynchTask value is %s\n",value);
            Thread t = new Thread(()->doSomething(value));
            t.start();
            value = -1;
            t.join();
        }

        public void doSomething(int value)
        {
            System.out.printf("In asynch task, value is %s\n",value);
        }
    }
}
我发现输出令人惊讶。是的

In launchAsynchTask value is 10
In asynch task, value is -1
因为我最初(在JLS研究之前)直觉地期望lambda捕捉变量
的值,而不是对它的引用

如果我必须保证捕获当前值而不是引用,那么显而易见的解决方案是创建本地最终临时值:

        final int capture = this.value;
        Thread t = new Thread(()->doSomething(capture));
我的问题是:这是一种公认的惯用方法来强制获取价值,还是有其他更自然的方法来实现这一点

我。。。直觉上期望lambda捕获变量值的值,而不是对它的引用

这(捕获值)就是局部变量发生的情况

对于字段,实际发生的情况是捕获对字段所属对象实例的引用。在您的例子中,它是对
启动器的引用。这个
对象。(在声明内部类时也会发生同样的情况。)

我的问题是:这是一种公认的惯用方法来强制获取价值,还是有其他更自然的方法来实现这一点


我想不出更好的方法了。

因为你使用的是速记语法,所以发生的事情并不那么明显

当您写入
value
以访问该字段时,它隐式地表示
this.value

lambda表达式捕获的是所有非静态方法都隐含的绝对最终“局部变量”

lambda表达式

()->doSomething(value)
逻辑上等同于

new Lambda$1(this)
其中
Lambda$1
声明如下(使用任意名称):

如您所见,lambda表达式
()->doSomething(value)
实际上并没有捕获
值。不合格的字段访问掩盖了实际发生的情况


仅供参考:在
doSomething()
方法中将字段
value
隐藏在参数
value
后面是个坏主意。名称冲突使得代码很容易被程序员误解,好的IDE会就此向您发出警告(除非您禁用该警告)


希望这只是在创建MCVE时发生的错误,而您不会在实际代码中这样做。:-)

我通常喜欢做的是最小化直接访问字段的代码部分,这样您就可以将启动线程的部分封装在如下函数中:

public void launchAsynchTask()引发异常
{
System.out.printf(“In-launchAsynchTask值为%s\n”,this.value”);
线程t=launchAsynchTaskWithValue(this.value);
这个值=-1;
t、 join();
}
公共线程launchAsynchTaskWithValue(int launchValue)引发异常
{
线程t=新线程(()->doSomething(启动值));
t、 start();
返回t;
}
有人可能会说,防止这里出现问题的“公认惯用方法”是使类不可变。这样,字段<代码>值
也是“有效的最终值”。当然,一个人必须是一个专门的函数式程序员才能提出这个论点。(我不是他们中的一员)在Java中实现这一点会产生根本性的破坏性影响,以至于lambdas会死在水里。。。在大多数Java程序员的头脑中。
private static final class Lambda$1 implements Runnable {
    private final Launcher ref;
    Lambda$1(Launcher ref) {
        this.ref = ref;
    }
    @Override
    public void run() {
        this.ref.doSomething(this.ref.value);
    }
}