Home > Net >  How can you initialize a captured variable inside a lambda expression
How can you initialize a captured variable inside a lambda expression

Time:07-09

In Java, it is only possible to capture final (or effectively final) variables in lambda expressions. It is possible to declare a final variable first and then initialize it once, but not if the initialization occurs in a lambda expression. This is ok:

final int num;
num = 10;

But this is not:

final int num;
Thread thread = new Thread(() -> {
    num = 10;
});
thread.start();

Even if run() is used which doesn't create new threads, it doesn't compile, neither will any other lambda expression. This makes sense since you could execute lambda expressions multiple times, which would mean trying to reassign a final variable.

But it seems like it could be useful to initialize variables in a lambda expression e.g. Maybe you have some long process to get and store a value in a variable like reading a large file; but because it is long, you want it to happen in a new thread so the current one can move on.

The only solution I know is to define the variable as a one-element array which allows you to change the element. This seems to me somewhat unintuitive and like a way to cheat the language instead of writing good code. Are there better designs which are good for attacking this problem?

CodePudding user response:

The best fit for your example is to submit() a Callable to an ExecutorService. Later, when you need the result, you access it via the resulting Future.

Alternatively, if the result is not needed in the main thread, and can be handled asynchronously, you can compose a CompletableFuture to perform your task.

CodePudding user response:

Even if run() is used which doesn't create new threads, it doesn't compile, neither will any other lambda expression. This makes sense since you could execute lambda expressions multiple times, which would mean trying to reassign a final variable.

Inside a lambda expression, you can initialize variables in the enclosing code. You can only effectively final variables inside lambda expressions. It's not only related to lambdas, the same applies to anonymous classes and that restriction exists from the earlier days of Java. Java 8 only brought us the notion of effectively final variables.

Here's a simplified answer:

I guess you know that there are two memory areas: the heap where all objects reside and a stack in which is meant to keep the data related to each method call.

Variables related to a particular method call form a layer of the stack, i.e. when a method is being called a new layer gets allocated on the top of the stack, when it calls another method - another layer gets allocated on top of this layer. Even when a variable is being passed around between different layers, each stack layer has its own version of this variable and their values are completely independent. When a method terminates all its layer is being deallocated, and we're loosing access to all its variables, the layer beneath becomes active.

Lambda as well as an instance of the anonymous class are objects that reside in the heap. If there's at least one reference that leads to them on any of the layers of the stack, they would be alive and can sustain much longer than variables of the layer where they have been created. Therefore, it would have no sense if we imagine that it would be allowed to change a local variable from an anonymous class or lambda expression because there's no guarantee that these variables would exist at the moment.

When a lambda or anonymous class is being created, JVM creates a copy of everything that is accessible. If a local variable has not been initialized, it would not be captured because it's not effectively final.

You can find a very simple definition of effectively final variable at the very end of the paragraph 4.12.4. of the Java language specification

If a variable is effectively final, adding the final modifier to its declaration will not introduce any compile-time errors. Conversely, a local variable or parameter that is declared final in a valid program becomes effectively final if the final modifier is removed.

If variable isn't initialized at neither at the moment of declaration, no anywhere in the code, then if you would try to add final modifier to its declaration, it'll cause a compilation. Such variable is not effectively final.

CodePudding user response:

Better designs use purpose-built features, such as a CompletableFuture. This provides mechanisms for the asynchronous code to set a return value, for the consumer of the value to (optionally) wait and get the return value, or for subsequent operations to be invoked when the return value becomes available.

  • Related