Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mutating instance or local object variables in Lambda java 8

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:

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

 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?

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?

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?

like image 951
Nestor Hernandez Loli Avatar asked Mar 23 '14 23:03

Nestor Hernandez Loli


People also ask

Can we change the lambda expression variable data?

Yes, you can modify local variables from inside lambdas (in the way shown by the other answers), but you should not do it.

Can we use local variable in lambda Java?

A lambda expression can't define any new scope as an anonymous inner class does, so we can't declare a local variable with the same which is already declared in the enclosing scope of a lambda expression. Inside lambda expression, we can't assign any value to some local variable declared outside the lambda expression.

Can we use instance variable in lambda expression?

A Lambda expression can also access an instance variable. Java Developer can change the value of the instance variable even after its defined and the value will be changed inside the lambda as well.

Why local variables in lambda must be final?

Forcing the variable to be final avoids giving the impression that incrementing start inside the lambda could actually modify the start method parameter.


1 Answers

Your assumptions are incorrect.

You can only change effectively final variables 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.

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

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

  • 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.

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.

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

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.

like image 115
skiwi Avatar answered Oct 21 '22 00:10

skiwi