Home > Back-end >  Using effectively final variable
Using effectively final variable

Time:01-03

Question related to frames of function and effective finality of variables.

int lenMin = list.get(0).length(); 

for (String s : list)
  if (s.length() < lenMin)
    lenMin = s.length();

list
  .stream()
  .filter(s -> lenMin == s.length())
  .forEach(System.out::println);

st:1. here we create a variable. st: 3-5 here we do something with it (change it). st:9 the lenMin variable is underlined in red.

Effectivity finality we cannot change, but can we use it? I know that the lambda has a new frame, but if we do this:

int len = lenMin;
list
  .stream()
  .filter(s -> len == s.length())
  .forEach(System.out::println);

then there will be no error.

Please explain why this is happening?

CodePudding user response:

Functions that can close over variables need a way to capture those variables.

If a variable can't change, then you can easily capture variables by copying their values.

Java does exactly that. Java lambdas, and inner classes simply correspond to classes that have an extra field for each captured variable.

The JVM only understands flat classes; nested and inner classes are compiler fictions.

Runnable f() {
  int answer = 42;
  class C extends Runnable {
    public void run() { System.out.println(answer); }
  }
  return new C();
}

compiles to something equivalent to

class C {
  private int answer; // Capturing field
  C(int answer) { this.answer = answer }
  void run() { System.out.println(this.answer); }
}
Runnable f() {
  int answer = 42;
  return new C(answer);
}

This change is called closure conversion. As long as answer is only assigned once, javac can do simple closure conversion: copying answer into any classes and lambdas that need it.

But simple copying doesn't work if a local variable might change after being captured.

int answer = 42;
Runnable c = new C();
answer  = 1;
c(); // Should this print 42 or 43?

Programmers familiar with languages that allow for reassignment of closed-over variables would get confused if the above didn't behave like the JavaScript below.

let answer = 42;
let c = () => console.log(answer);
answer  = 1;
c(); // Logs 43

Java could allow for mutable closed-over variables by doing more complicated closure conversion. For example, instead of copying answer, storing answer in a shared object and rewriting reads&writes to be reads/writes of a property or element of that shared object.

For example,

int[] answerBox = new int[1];
answerBox[0] = 42; // assignment
...
answerBox[0] = 43; // re-assignment
...
  System.out.println(answerBox[0]);
...

Java's designers decided not to do that though; capturing by-reference the way JavaScript does would introduce a host of subtle complications. JavaScript is single threaded, but the JVM is not, so imagine when a closed over long variable is suddenly assigned in more than one thread.


This is not hard to work around though as long as you don't need to mutate captured state after creating the capturing value.

Runnable f() {
  int x = 1;
  x  = 1;
  return () -> System.out.println(x); // ERROR
}

The above fails, but the below works; we simply store the state of the non-effectively-final x in an effectively final finalX that the lambda can capture.

Runnable f() {
  int x = 1;
  x  = 1;
  int finalX = x;
  return () -> System.out.println(finalX); // OK
}

CodePudding user response:

So the question is: why one has to introduce a new copy, to have a variable, that is effectively final (= could be made final). Here len.

int len = lenMin;
list
  .stream()
  .filter(s -> len == s.length())
  .forEach(System.out::println);

The reason that the lambda (inside filter) is an anonymous implemented interface instance, that uses len as follows: as it is a local variable, it copies it into a local interface variable with the same name len.

But now one has two variables (though same names). As the lambda theoretically could run in another thread, one has two threads that can change same named variables differently. The language designers did not want to allow two lens with different values, and simply required that the variable is not changed, effectively final.

Here it would not be dangerously misleading, but assume you later assign to len at the end or inside a lambda, and perhaps use .parallelStream(). Then suddenly a compile error must be given at an unchanged piece of code. That is an additionally sufficient reason to keep the rule simple: a local variable used inside a lambda must be effectively final.

  • Related