Lambda捕获实例变量

时间:2016-12-11 07:33:02

标签: java lambda

在令人沮丧的调试会话之后阅读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捕获变量value而不是对它的引用。

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

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

我的问题:这是强制价值捕获的惯用方法,还是有其他更自然的方式来做?

3 个答案:

答案 0 :(得分:4)

  

我...直观地期望lambda捕获变量值的值而不是对它的引用。

那个(捕获值)是局部变量的结果。

对于字段,实际发生的是您正在捕获对该字段所属对象实例的引用。在您的情况下,它是对my_dropdownMenu = function(..., type = c("messages", "notifications", "tasks"), badgeStatus = "primary", icon = NULL, .list = NULL) { message("Got here!") # ... # (COPY AND PASTE body(mydropdownMenu) HERE) # (and then make your modifications) } # helper function, see # https://stat.ethz.ch/pipermail/r-help/2008-August/171217.html rebindPackageVar = function(pkg, name, new) { # assignInNamespace() no longer works here, thanks nannies ns=asNamespace(pkg) unlockBinding(name,ns) assign(name,new,envir=ns,inherits=F) assign(name,new,envir=globalenv()) lockBinding(name,ns) } # make sure we can call non-exported functions (like validateStatus()) environment(my_dropdownMenu) = asNamespace("shinydashboard") # now rebind the dropdownMenu function rebindPackageVar("shinydashboard", "dropdownMenu", my_dropdownMenu); 对象的引用。 (当你声明一个内部类时会发生同样的事情。)

  

我的问题:这是强制价值捕获的惯用方法,还是有其他更自然的方式来做?

我想不出更好的方法。

答案 1 :(得分:2)

因为您使用速记语法,所以发生的事情并不明显。

当您编写value来访问字段时,它隐含地表示this.value

lambda表达式捕获绝对最终的"局部变量" this隐含于所有非静态方法。

lambda表达式

()->doSomething(value)

逻辑上等同于

new Lambda$1(this)

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

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

仅供参考:value方法中隐藏参数value后面的doSomething()字段是一个坏主意。名称冲突使得代码非常容易被程序员误解,好的IDE会警告你(除非你禁用了那个警告)

希望在创建MCVE时错误地发生在这里,你不会在实际代码中这样做。 : - )

答案 2 :(得分:1)

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

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