Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mutating `free variables` of Lambda Expressions

Tags:

java

java-8

final

I'm reading this fantastic article about Lambda Expressions and the following is uncleared to me:

  1. Does Lambda Expression saves the value of the free-variables or refernse/pointer to each of them? (I guess the answer is the latter because if not, mutate free-variables would be valid).

Don't count on the compiler to catch all concurrent access errors. The prohibition against mutation holds only for local variables.

I'm not sure that self experimenting would cover all the cases so I'm searching for a well defined rules about:

  1. What free varibles can be mutated inside the Lambda Expression (static/properties/local variables/parameters) and which can be mutated out side while beeing used inside a Lambda Expression?
  2. Can I mutate every free variable after the end of a block of a Lambda Expression after I used it (read or called one of his methods) inisde a Lambda Expression?

Don't count on the compiler to catch all concurrent access errors. The prohibition against mutation holds only for local variables. If matchesis an instance or static variable of an enclosing class, then no error is reported, even though the result is just as undefined.

  1. Does the result of the mutation is undefined even when I use a synchroniziton algorithm?

Update 1:

free variables - that is, the variables that are not parameters and not defined inside the code.

In simple words I can conclude that Free variables are all the variables that are not parameters of the Lambda Expression and are not defined inside the same Lambda Expression ?

like image 575
Stav Alfi Avatar asked Dec 13 '22 23:12

Stav Alfi


1 Answers

This looks like complicated "words" on a simpler topic. The rules are pretty much the same as for anonymous classes.

For example the compiler catches this:

 int x = 3;

 Runnable r = () -> {
    x = 6; // Local variable x defined in an enclosing scope must be final or effectively final
 };

But at the same time it is perfectly legal to do this(from a compiler point of view):

    final int x[] = { 0 };

    Runnable r = () -> {
        x[0] = 6;
    };

The example that you provided and uses matches:

 List<Path> matches = new ArrayList<>();
    List<Path> files = List.of();
    for (Path p : files) {
        new Thread(() -> {
            if (1 == 1) {
                matches.add(p);
            }
        }).start();
    }

has the same problem. The compiler does not complain about you editing matches(because you are not changing the reference matches - so it is effectively final); but at the same time this can have undefined results. This operation has side-effects and is discouraged in general. The undefined results would come from the fact that your matches is not a thread-safe collection obviously.

And your last point : Does the result of the mutation is undefined even when I use a synchroniziton algorithm?. Of course not. With proper synchronization updating a variable outside lambda(or a stream) will work - but are discouraged, mainly because there would be other ways to achieve that.

EDIT

OK, so free variables are those that are not defined within the lambda code itself or are not the parameters of the lambda itself.

In this case the answer to 1) would be: lambda expressions are de-sugared to methods and the rules for free-variables are the same as for anonymous classes. This has been discussed numerous times, like here. This actually answers the second question as well - since the rules are the same. Obviously anything that is final or effectively final can be mutated. For primitives - this means they can't be mutated; for objects you can't mutate the references (but can change the underlying data - as shown in my example). For the 3) - yes.

like image 54
Eugene Avatar answered Dec 28 '22 12:12

Eugene