Search code examples
javaandroidmultithreadinglambdafinal

How can I access a non-final variable in a Thread lambda?


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;

Solution

  • 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;

    • for your get(endpoint) method to return a mutable object, and
    • a way for a caller to wait until a new value has been stored into the mutable object by some other thread.

    The 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());
    }