如何在Thread lambda中访问非final变量?

huangapple go评论81阅读模式
英文:

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

问题

我有一个自定义对象,需要在一个Thread lambda内部进行修改,因为我需要执行一个操作并为其分配一些值。

问题是,当我在Thread()中声明变量时,无法从封闭函数中返回它。然后,如果我尝试将其变成全局变量并在Thread内部赋值,那是不可能的,因为lambda只允许在其中使用final或有效final的变量。

有什么解决方法/解决方案可以解决这个问题?

// 给出了不期望的结果
public class MeClass {
    public static Response response = new Response();

    // TODO: 使response只针对一个方法具体化,而不是全局的

    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;
    }
    // 具有类似功能且访问response的另一个方法

}

所以我想在方法内部声明response,但由于只有final变量可用,我无法这样做。

// 给出了一个错误
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;
}
英文:

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;

答案1

得分: 1

假设这是被允许的?你会期望它返回什么?

    // 警告!这是一个示范,不应该这么做。
    //
    public Response get(String endpoint) {
        Response response = new Response();
        new Thread(() -> {
            response = OffredUtil.makeGetRequest(endpoint);
        }).start();
        return response;
    }

没有理由认为 response = OffredUtil.makeGetRequest(endpoint); 这个语句会在 return response; 语句之前执行。实际上,它很可能要稍后才会被执行。

你真正想要的是:

  • 使你的 get(endpoint) 方法返回一个可变对象,以及
  • 让调用者能够等待,直到其他线程将新值存储到可变对象中。

Java 标准库定义了一个适用于这种可变对象的接口:它叫做 java.util.concurrent.FutureFuture 有一个 get() 方法,如果必要的话,它会等待,直到某个其他线程通过给它一个值来完成该 Future,然后 get() 将返回这个值。

最简单的使用方式是通过 CompletableFuture 类:

    import java.util.concurrent.Future;
    import java.util.concurrent.CompletableFuture;
    ...
    public Future<Response> get(String endpoint) {
        return CompletableFuture.supplyAsync(() -> {
           return OffredUtil.makeGetRequest(endpoint);
        });
    }

对这个 get(endpoint) 方法的调用将会向内置线程池提交一个任务,该任务将执行给定的 lambda 表达式,然后返回一个将被该任务完成的 Future

如果 lambda 产生一个值,那么它将成为 Future 的值。如果 lambda 抛出异常,那么它会被捕获,并且异常对象将被存储在 Future 中。

调用 get(endpoint) 的调用者可以这样做:

    ...
    Future<Response> fr = myClassInstance.get(endpoint);
    与 makeGetRequest 调用同时进行其他操作(...);
    try {
        Response r = fr.get();
        ...
    } catch (Exception e) {
        o.response.isException = true;
        Log.d(TAG, e.getMessage());
    }
英文:

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(() -&gt; {
        response = OffredUtil.makeGetRequest(endpoint);
    }).start();
    return response;
}

There's no reason to think that response&#160;=&#160;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&lt;Response&gt; get(String endpoint) {
    return CompletableFuture.supplyAsync(() -&gt; {
       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&lt;Response&gt; fr = myClassInstance.get(endpoint);
doSomethingElseConcurrentlyWithThe_makeGetRequest_call(...);
try {
    Response r = fr.get();
    ...
} catch (Exception e) {
    o.response.isException = true;
    Log.d(TAG, e.getMessage());
}

huangapple
  • 本文由 发表于 2020年9月4日 16:49:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/63737848.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定