简体   繁体   English

Lambda 捕获实例变量

[英]Lambda capturing instance variable

From reading the JLS after a frustrating debugging session I find that lambdas will capture the value of effectively-final local variables, but if you refer to an instance variable it captures a reference to the variable, which has serious implications for multi-threaded code.在令人沮丧的调试会话之后阅读 JLS,我发现 lambdas 将捕获有效最终局部变量的,但如果您引用实例变量,它会捕获对变量的引用,这对多线程代码有严重影响。

For example, the following is an MCVE distilled from a much larger program:例如,以下是从一个更大的程序中提炼出来的 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);
        }
    }
}

I found the output surprising.我发现输出令人惊讶。 It is这是

In launchAsynchTask value is 10
In asynch task, value is -1

since I initially (prior to JLS research) and intuitively expected the lambda to capture the value of the variable value instead of a reference to it.因为我最初(在 JLS 研究之前)并且直观地期望 lambda 捕获变量value而不是对它的引用。

If I have to guarantee that the current value is captured instead of a reference the obvious solution is to create a local final temporary:如果我必须保证捕获当前而不是引用,那么明显的解决方案是创建一个本地最终临时值:

        final int capture = this.value;
        Thread t = new Thread(()->doSomething(capture));

My question: Is this the accepted idiomatic way to force value capture, or is there some other more natural way to do it?我的问题:这是强制获取价值的公认惯用方式,还是有其他更自然的方式来做到这一点?

I ... intuitively expected the lambda to capture the value of the variable value instead of a reference to it.我......直观地期望 lambda 捕获变量 value 的值而不是对它的引用。

That (capturing the value) is what happens with local variables.那(捕获值)就是局部变量发生的事情。

With fields, what is actually happening is that you are capturing a reference to the instance of the object that the field belongs to.对于字段,实际发生的是您正在捕获对该字段所属对象实例的引用。 In your case, it is a reference to the Launcher.this object.在您的情况下,它是对Launcher.this对象的引用。 (The same thing happens when you declare an inner class.) (当你声明一个内部类时也会发生同样的事情。)

My question: Is this the accepted idiomatic way to force value capture, or is there some other more natural way to do it?我的问题:这是强制获取价值的公认惯用方式,还是有其他更自然的方式来做到这一点?

I can't think of a better way.我想不出更好的办法。

Because you're using shorthand syntax, it's not as obvious what is going on.因为您使用的是速记语法,所以发生的事情并不那么明显。

When you write value to access the field , it implicitly means this.value .当您写入value以访问该字段时,它隐含地表示this.value

The lambda expression is capturing the absolutely final "local variable" this that is implicit to all non-static methods. lambda 表达式正在捕获对所有非静态this隐含的绝对最终“局部变量”。

The lambda expression拉姆达表达式

()->doSomething(value)

is logically equivalent to逻辑上等价于

new Lambda$1(this)

where Lambda$1 is declared like this (using arbitrary names) :其中Lambda$1声明如下(使用任意名称)

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

As you can see, the lambda expression ()->doSomething(value) is not actually capturing value .如您所见,lambda 表达式()->doSomething(value)实际上并未捕获value The unqualified field access is obscuring what is actually happening.不合格的字段访问掩盖了实际发生的事情。


FYI: Hiding field value behind parameter value in the doSomething() method is a bad idea.仅供参考:doSomething()方法中将字段value隐藏在参数value后面是一个坏主意。 The name conflict makes the code very vulnerable to misinterpretation by programmers, and good IDEs will warn you about it (unless you disabled that warning) .名称冲突使代码很容易被程序员误解,并且好的 IDE 会警告您(除非您禁用该警告)

Hopefully that just happened by mistake here when creating an MCVE, and you wouldn't do that in real code.希望这只是在创建 MCVE 时发生的错误,而您不会在实际代码中这样做。 :-) :-)

What I normally like to do is to minimize code parts that access fields directly, so you could wrap the part starting the thread in a function like this:我通常喜欢做的是最小化直接访问字段的代码部分,因此您可以将启动线程的部分包装在这样的函数中:

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

public Thread launchAsynchTaskWithValue(int launchValue) throws Exception
{
    Thread t = new Thread(()->doSomething(launchValue));
    t.start();
    return t;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM