在 Lambda java 8 中改变实例或局部对象变量

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22598665/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 16:45:06  来源:igfitidea点击:

Mutating instance or local object variables in Lambda java 8

javalambdajava-8

提问by Nestor Hernandez Loli

I know that for concurrency reasons I cannot update the value of a local variable in a lambda in Java 8. So this is illegal:

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

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

But, what about updating an instance variable or changing the state of a local object?, For example a Swing application I have a button and a label declared as instance variables, when I click the button I want to hide the label

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

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

I get no compiler errors and works fine, but... is it right to change state of an object in a lambda?, Will I have concurrency problems or something bad in the future?

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

Here another example. Imagine that the following code is in the method doGet of a servlet Will I have some problem here?, If the answer is yes: Why?

这里再举一个例子。想象一下,下面的代码在一个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); 

What I want to know is: When is it right to mutate the state of an object instance in a lambda?

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

采纳答案by Maurice Naftalin

In general: yes, you may get concurrency problems, but only the ones you already had. Lambdafying it won't make code non-threadsafe where it was before, or vice versa. In the example you give, your code is (probably) threadsafe because an ActionListeneris only ever called on the event-dispatching thread. Provided you have observed the Swing single-threaded rule, no other thread ever accesses jLabel, and if so there can be no thread interference on it. But that question is orthogonal to the use of lambdas.

一般而言:是的,您可能会遇到并发问题,但仅限于您已经遇到的问题。Lambdafying 它不会使代码在之前的地方变得非线程安全,反之亦然。在您给出的示例中,您的代码(可能)是线程安全的,因为 anActionListener仅在事件调度线程上被调用。如果您遵守了 Swing 单线程规则,则不会有其他线程访问jLabel,如果是,则不会有线程干扰。但是这个问题与 lambda 的使用是正交的。

回答by piotrek

in case 'forEach' is distributed to different threads/cores you might have concurrency issues. consider using atomics or concurrent structures (like ConcurrentHashMap)

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

回答by skiwi

Your assumptions are incorrect.

你的假设是不正确的。

You can only change effectively finalvariables in lambdas, because lambdas are syntactic sugar* over anonymous inner classes.
*They are actually more than only syntactic sugar, but that is not relevant here.

您只能有效地更改lambda 中的最终变量,因为 lambda 是匿名内部类的语法糖*。
*它们实际上不仅仅是语法糖,但这在这里无关紧要。

And in anonymous inner classes you can only change effectively finalvariables, hence the same holds for lambdas.

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

You can do anything you want with lambdas as long as the compiler allows it, onto the behaviour part now:

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

  • If you modify state that depends on other state, in a parallel setting, then you are in trouble.
  • If you modify state that depends on other state, in a linear setting, then everything is fine.
  • If you modify state that does not depend on anything else, then everything is fine as well.
  • 如果您在并行设置中修改依赖于其他状态的状态,那么您就有麻烦了。
  • 如果您在线性设置中修改依赖于其他状态的状态,那么一切都很好。
  • 如果您修改不依赖于其他任何东西的状态,那么一切也都很好。

Some examples:

一些例子:

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

This will print 1000000 as expected no matter what happens, even though it depends on the previous state.

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

Now let's parallelize the stream:

现在让我们并行化流:

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

Now it prints different integers, like 199205, or 249165, because other threads are not always seeing the changes that different threads have made, because there is no synchronization.

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

But say that we now get rid of our dummy class and use the AtomicInteger, which is thread-safe, we get the following:

但是说,我们现在摆脱了伪类和使用AtomicInteger,这是线程安全的,我们得到如下:

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

Now it correctly prints 1000000 again.
Synchronization is costly however, and we have lost nearly all benefits of parallelization here.

现在它再次正确打印 1000000。
然而,同步是昂贵的,我们几乎失去了并行化的所有好处。