Java Lambda捕获实例变量
在令人沮丧的调试会话之后阅读JLS,我发现lambdas将有效地捕获最终局部变量的值,但如果引用实例变量,它将捕获对该变量的引用,这对多线程代码有严重影响 例如,以下是从更大的程序中提取的MCVE: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
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);
}
}