在Lambda java 8中改变实例或本地对象变量

时间:2014-03-23 23:42:48

标签: java lambda java-8

我知道出于并发原因,我无法在Java 8中更新lambda中局部变量的值。所以这是非法的:

double d = 0;
orders.forEach( (o) -> {
     d+= o.getTotal(); 
});

但是,如何更新实例变量或更改本地对象的状态?例如,Swing应用程序我有一个按钮和一个声明为实例变量的标签,当我单击按钮时我想隐藏标签< / p>

 jButton1.addActionListener((  e) -> {
      jLabel.setVisible(false);
 });

我没有编译器错误并且工作正常,但是......改变lambda中对象的状态是正确的吗?我将来会遇到并发问题还是坏事?

这是另一个例子。想象一下,以下代码位于servlet的方法doGet中 我会在这里遇到一些问题吗?如果答案是肯定的:为什么?

String key = request.getParameter("key");

Map<String, String> resultMap = new HashMap<>();  

Map<String, String> map = new HashMap<>();
//Load map

map.forEach((k, v) -> {
    if (k.equals(key)) {
        resultMap.put(k, v);
    }
});
 response.getWriter().print(resultMap); 

我想知道的是:什么时候改变lambda中对象实例的状态是正确的?

3 个答案:

答案 0 :(得分:14)

您的假设不正确。

你只能在lambdas中更改有效的最终变量,因为lambdas是匿名内部类的语法糖*。  *它们实际上不仅仅是语法糖,但这与此无关。

在匿名内部类中,您只能更改有效的最终变量,因此对于lambdas也是如此。

只要编译器允许,你就可以用lambdas做任何你想做的事情,现在进入行为部分:

  • 如果修改依赖于其他状态的状态,则在并行设置中,则遇到麻烦。
  • 如果修改依赖于其他状态的状态,则在线性设置中,一切都很好。
  • 如果你修改了不依赖于其他任何东西的状态,那么一切都很好。

一些例子:

class MutableNonSafeInt {
    private int i = 0;

    public void increase() {
        i++;
    }

    public int get() {
        return i;
    }
}

MutableNonSafeInt integer = new MutableNonSafeInt();
IntStream.range(0, 1000000)
        .forEach(i -> integer.increase());
System.out.println(integer.get());

无论发生什么情况,都会按预期打印1000000,即使它取决于之前的状态。

现在让我们并行化流:

MutableNonSafeInt integer = new MutableNonSafeInt();
IntStream.range(0, 1000000)
        .parallel()
        .forEach(i -> integer.increase());
System.out.println(integer.get());

现在它打印不同的整数,如199205或249165,因为其他线程并不总是看到不同线程所做的更改,因为没有同步。

但是说我们现在摆脱了我们的虚拟类并使用AtomicInteger是线程安全的,我们得到以下内容:

AtomicInteger integer = new AtomicInteger(0);
IntStream.range(0, 1000000)
        .parallel()
        .forEach(i -> integer.getAndIncrement());
System.out.println(integer.get());

现在它再次正确打印1000000 然而,同步成本很高,而且我们几乎失去了并行化的所有好处。

答案 1 :(得分:5)

一般情况下:是的,您可能会遇到并发问题,但只会遇到已经存在的并发问题。 Lambdafying它不会使代码非线程安全在以前,或反之亦然。在您给出的示例中,您的代码(可能)是线程安全的,因为只在事件派发线程上调用ActionListener。如果您已观察到Swing单线程规则,则没有其他线程访问jLabel,如果是,则不会对其进行线程干扰。但这个问题与使用lambdas是正交的。

答案 2 :(得分:2)

如果“forEach”分发到不同的线程/核心,则可能会出现并发问题。考虑使用原子或并发结构(如ConcurrentHashMap)