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.

1
  • 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. Commented Apr 12, 2016 at 7:31

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

7 Comments

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...
"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 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.
@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.
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.
|
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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.