Lambdas: local variables need final, instance variables don't

85,055

Solution 1

The fundamental difference between a field and a local variable is that the local variable is copied when JVM creates a lambda instance. On the other hand, fields can be changed freely, because the changes to them are propagated to the outside class instance as well (their scope is the whole outside class, as Boris pointed out below).

The easiest way of thinking about anonymous classes, closures and labmdas is from the variable scope perspective; imagine a copy constructor added for all local variables you pass to a closure.

Solution 2

In document of project lambda : State of the Lambda v4

Under Section 7. Variable capture, It is mentioned that....

It is our intent to prohibit capture of mutable local variables. The reason is that idioms like this:

int sum = 0;
list.forEach(e -> { sum += e.size(); });

are fundamentally serial; it is quite difficult to write lambda bodies like this that do not have race conditions. Unless we are willing to enforce—preferably at compile time—that such a function cannot escape its capturing thread, this feature may well cause more trouble than it solves.

Edit :

Another thing to note here is, local variables are passed in constructor of inner class when you access them inside your inner class, and this won't work with non final variable because value of non-final variables can be changed after construction.

While in case of instance variable, compiler passes reference of class and class' reference will be used to access instance variable. So it is not required in case of instance variables.

PS : It is worth mentioning that, anonymous classes can access only final local variables (in JAVA SE 7), while in Java SE 8 you can access effectively final variables also inside lambda as well as inner classes.

Solution 3

In Java 8 in Action book, this situation is explained as:

You may be asking yourself why local variables have these restrictions. First, there’s a key difference in how instance and local variables are implemented behind the scenes. Instance variables are stored on the heap, whereas local variables live on the stack. If a lambda could access the local variable directly and the lambda were used in a thread, then the thread using the lambda could try to access the variable after the thread that allocated the variable had deallocated it. Hence, Java implements access to a free local variable as access to a copy of it rather than access to the original variable. This makes no difference if the local variable is assigned to only once—hence the restriction. Second, this restriction also discourages typical imperative programming patterns (which, as we explain in later chapters, prevent easy parallelization) that mutate an outer variable.

Solution 4

Because instance variables are always accessed through a field access operation on a reference to some object, i.e. some_expression.instance_variable. Even when you don't explicitly access it through dot notation, like instance_variable, it is implicitly treated as this.instance_variable (or if you're in an inner class accessing an outer class's instance variable, OuterClass.this.instance_variable, which is under the hood this.<hidden reference to outer this>.instance_variable).

Thus an instance variable is never directly accessed, and the real "variable" you're directly accessing is this (which is "effectively final" since it is not assignable), or a variable at the beginning of some other expression.

Solution 5

Putting up some concepts for future visitors:

Basically it all boils down to the point that compiler should be able to deterministically tell that lambda expression body is not working on a stale copy of the variables.

In case of local variables, compiler has no way to be sure that lambda expression body is not working on a stale copy of the variable unless that variable is final or effectively final, so local variables should be either final or effectively final.

Now, in case of instance fields, when you access an instance field inside the lambda expression then compiler will append a this to that variable access (if you have not done it explicitly) and since this is effectively final so compiler is sure that lambda expression body will always have the latest copy of the variable (please note that multi-threading is out of scope right now for this discussion). So, in case instance fields, compiler can tell that lambda body has latest copy of instance variable so instance variables need not to be final or effectively final. Please refer below screen shot from an Oracle slide:

enter image description here

Also, please note that if you are accessing an instance field in lambda expression and that is getting executed in multi-threaded environment then you could potentially run in problem.

Share:
85,055

Related videos on Youtube

Gerard
Author by

Gerard

Updated on January 19, 2020

Comments

  • Gerard
    Gerard over 4 years

    In a lambda, local variables need to be final, but instance variables don't. Why so?

    • McDowell
      McDowell almost 10 years
    • Valentin Ruano
      Valentin Ruano about 6 years
      Let be known that at least with the latest version of the compiler java 1.8 local variables only need to be effectively final so they don't need to be declared final per se but the cannot be modified.
    • flow2k
      flow2k over 5 years
      After reading all the answers here, I still think it's just a rule enforced by the compiler, designed to minimize programmer error - that is, there is no technical reason why mutable local variables can't be captured, or why captured local variables can't be mutated, for that matter. This point is supported by the fact that this rule can be readily circumvented by using an object wrapper (so the object reference is effectively final, but not the object itself). Another way is to create an array, i.e. Integer[] count = {new Integer(5)}. See also stackoverflow.com/a/50457016/7154924.
    • Pacerier
      Pacerier about 4 years
      @McDowell, lambda's are not merely syntax sugar for anonymous classes but a different construct altogether.
  • Gerard
    Gerard almost 10 years
    Yes, but instance variables can be referenced and assigned in a lambda, which is surprising to me. Only local variables have the final limitation.
  • Boris the Spider
    Boris the Spider almost 10 years
    @Gerard Because an instance variable has the scope of the whole class. This is exactly the same logic as for an anonymous class - there are plenty of tutorials explaining the logic.
  • Gerard
    Gerard almost 10 years
    Right, then the anonymous class constructor doesn't need to copy the instance variable because it can just reference it. Good explanation!
  • Hearen
    Hearen about 6 years
    I really think there are some issues in Java 8 in Action in this point. If the local variable here refers to the variables created in the method but accessed by the lambdas, and the multi-thread is achieved by ForkJoin, then there will be a copy for different threads and mutation in lambdas is acceptable theoretically, in which case it can be mutated. But the thing here is different, local variables used in lambda is for parallelisation achieved by parallelStream, and these local variables are shared by different threads which are based on the lambdas.
  • Hearen
    Hearen about 6 years
    So the first point is actually not right, there is no so-called copy, it's shared among threads in parallelStream. And sharing mutable variables among threads is dangerous just like the Second point. That's why we prevent it and introduce built-in methods in Stream to handle these cases.
  • flow2k
    flow2k over 5 years
    Would you mind providing the source of the Oracle slide?
  • Yug Singh
    Yug Singh over 5 years
    @hagrawal could you please elaborate your final statement regarding multi-threaded environment? Is it with respect to actual value of member variable at any time since many threads are running at same time so they can override the instance variable. Also, if I synchronize the member variables properly then also the problem remains?
  • Supun Wijerathne
    Supun Wijerathne over 5 years
    best answer for the question I guess ;)
  • Sritam Jagadev
    Sritam Jagadev over 5 years
    Nice explanation for this question