I have a custom object that I need to modify inside a Thread lambda, as I need to perform an operation and assign some value to it.
The problem is that when I declare the variable in the Thread(), it cannot be returned from the enclosing function. Then, if I try to make it a global variable and assign some value to it inside the Thread, it can't be done because lambdas only allow final or effectively final variables inside them.
What can be a workaround/solution for this?
// Gives an undesired result
public class MeClass {
public static Response response = new Response();
// TODO: Make response specific to a method and not global
public Response get(String endpoint) {
new Thread(() -> {
try {
this.response = OffredUtil.makeGetRequest(endpoint);
} catch (Exception e) {
this.response.isException = true;
Log.d(TAG, e.getMessage());
}
}).start();
return this.response;
}
// Another method with similar function accessing response
}
So I want to declare the response
inside the method itself, but I can't do it due to only final variables being available.
// Gives an error
public Response get(String endpoint) {
Response response = new Response();
new Thread(() -> {
try {
response = OffredUtil.makeGetRequest(endpoint);
} catch (Exception e) {
this.response.isException = true;
Log.d(TAG, e.getMessage());
}
}).start();
return response;
Suppose this was allowed? What would you expect it to return?
// Warning! This is an example of what *NOT* to do.
//
public Response get(String endpoint) {
Response response = new Response();
new Thread(() -> {
response = OffredUtil.makeGetRequest(endpoint);
}).start();
return response;
}
There's no reason to think that response = OffredUtil.makeGetRequest(endpoint);
statement will be executed until before the return response;
statement. In fact, it probably will not be executed until some time later.
What you really want is;
get(endpoint)
method to return a mutable object, andThe Java standard library defines an interface for just that kind of mutable object: It's called java.util.concurrent.Future
. A Future
has a get()
method that will wait, if necessary, until some other thread has completed the Future by giving it a value, and then the get()
will return the value.
The simplest way to use it is through the CompletableFuture
class:
import java.util.concurrent.Future;
import java.util.concurrent.CompletableFuture;
...
public Future<Response> get(String endpoint) {
return CompletableFuture.supplyAsync(() -> {
return OffredUtil.makeGetRequest(endpoint);
});
}
A call to this get(endpoint)
method will submit a task to a built-in thread pool that will execute the given lambda expression, and then it will return a Future
that will be completed by the task.
If the lambda produces a value, then that will become the value of the Future
. If the lambda throws an exception, then that will be caught and, and the exception object will be stored in the Future
The caller of get(endpoint)
can do this:
...
Future<Response> fr = myClassInstance.get(endpoint);
doSomethingElseConcurrentlyWithThe_makeGetRequest_call(...);
try {
Response r = fr.get();
...
} catch (Exception e) {
o.response.isException = true;
Log.d(TAG, e.getMessage());
}