0

Code:

int counter = 0;
int[] counterInArray = {1};
IntStream.range(1, 100)//couldn't compile since counter is not effectively final
        .forEach(x -> System.out.println(x + ": " + counter++));
IntStream.range(1, 100)//Works well
        .forEach(x -> System.out.println(x + ": " + counterInArray[0]++));
IntStream.range(1, 100).parallel()//parallel breaks the counter, so we should be careful
        .forEach(x -> System.out.println(x + ": " + counterInArray[0]++));

As you see, we can do a simple hack(put it into an array) to make a variable effectively final and works well in single thread situation.

So why restrict variable to be effectively final in a Java lambda expression?

We have to hack it when we want to use some variable which is not effectively final but it works well with single thread stream.

You may read my post to see how hard to find a good way to hack down a counter(which is not effective final, of course) in lambda.

Community
  • 1
  • 1
Sayakiss
  • 6,878
  • 8
  • 61
  • 107
  • It may seem practical but it defeats very point of functional programming: Avoid state changes. Moreover: If you need a (thread-safe) counter then use a counter, not an int. The counter can be `final`. – a better oliver Apr 12 '16 at 07:31

2 Answers2

2

Captured variables work by copying their values. For example, suppose there is some anonymous inner class like this:

String message = "hello world!";
new Thread(new Runnable() {
    public void run() {
        System.our.println(message);
    }
}).start();

This will compile to code which is actually similar to the following:

class AnonymousRunnable0 implements Runnable {
    final String messageCopy;
    AnonymousRunnable0(String message) {
        this.messageCopy = message;
    }
    public void run() {
        System.out.println(this.messageCopy);
    }
}

String message = "hello world!";
new Thread(new AnonymousRunnable0(message)).start();

Capturing lambdas work in a similar fashion.

So the point is that when you capture variables, you do not actually have access to the outer scope as if with reference semantics, you just have copies of the values.

Also, if what you're asking for was possible, passing references to local variables around just makes for really wacky code. What if the lambda which captured the local variable gets put in a List somewhere and the method returns? Now you have a reference to a variable which doesn't exist anymore. This is the sort of thing Java is designed away from.

Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • `This is the sort of thing Java is designed away from.` So in your opinion, is it a good design or not? I think it just add an useless restriction and results a lot of weird hack... – Sayakiss Apr 12 '16 at 02:41
  • *"I think it just add an useless restriction"* OK. So how would you solve the dangling reference problem, where you have a lambda that has access to a local variable which doesn't exist anymore? For reference, this can result in undefined behavior in C++. – Radiodef Apr 12 '16 at 02:57
  • 1
    @Radiodef In C#, every local variable capture results in an instance of an anonymous local class being created, and the variable is its field. Each access of the variable, whether in the method or through the resulting lambda instance, is done as if you captured an instance field. Java did not want this at all. – Savior Apr 12 '16 at 03:02
  • 1
    @Pillar Yes, although I was asking the question rhetorically because I don't think the OP understands what they are asking for. They say they think it's a "useless restriction" when it actually requires a complicated feature addition. – Radiodef Apr 12 '16 at 03:12
  • I think this is sound reasoning. We should note however, that this facility is available only for local variables. There's no restriction that fields of enclosing classes accessed in Lambda expressions or inner class methods are (effectively) final. – Kedar Mhaswade Apr 12 '16 at 03:34
  • 1
    Actually instance variables are more akin to the array member than a local variable -- it's "this" that is effectively final, but you can modify what it references. – Hank D Apr 12 '16 at 03:39
  • The anonymous class will actually be named `Class$Index` where `Class` is the name of the parent class and `Index` is the index of the next anonymous class, starting from 0. – ThePyroEagle Dec 29 '16 at 12:37
1

As often stated, referencing local variables from the outer context currently works by copying the contents of a variable, so ensuring that the variable never changes is a way to align the behavior imposed by the technical solution with the semantics of the code.

The only way to share a mutable local variable with a lambda expression or inner class would be by converting it into a field or array element, in other words to a shared heap variable. That would break the fundamental principle that local variables are, as their name suggests, local, in other words unshared, which has dramatic consequences for the thread safety.

If you make such a change to the Java programming language, you can throw away almost every book about thread safety or concurrent programming in Java in general. You suddenly can’t rely on local variables being unshared and thus intrinsically thread safe, you would have to search the entire method’s code to find out whether there is code sharing that variable.

That would be a big sacrifice, just for making a discouraged programming technique more convenient. If you don’t find a better way to implement your desired functionality than via a shared variable of the surrounding scope, you can do what you already have shown in your question, use an object or array to make the use of a heap variable explicit. This doesn’t differ from what the implementation had to do under the hood, if it allowed shared mutable local variables, but requiring to make it explicit doesn’t sacrifice the reliable local nature of local variables.

Holger
  • 285,553
  • 42
  • 434
  • 765