Java中的Callable和Supplier接口有何区别?

34

CallableSupplier是Java的两个函数式接口,分别位于java.util.concurrentjava.util.function包中,具有以下签名-

public interface Callable<V> {
    V call() throws Exception;
}

public interface Supplier<T> {
    T get();
}

有没有一些特定的使用情况,其中一个比另一个更合适?

请注意,call会抛出异常,而supply则不会。 (与Supplier相比) - Glains
@Sweeper 我在寻找一种可以传递值的函数式接口,这两种都似乎很适合该用例。 - arunkjn
4个回答

40

从各自的文档中可以看出它们的使用差异:

Callable:

一个返回结果并且可能抛出异常的任务。实现者定义了一个名为call的无参方法。

Callable接口类似于Runnable,两者都是为其实例有可能由另一个线程执行的类而设计

Supplier:

表示提供结果的供应商。

没有要求每次调用供应商时都返回新的或不同的结果。

这意味着调用者期望在调用Callable.call时会抛出异常,并相应地处理异常。这对于像读写文件这样的任务非常有用,因为可能会抛出许多种IOExceptionCallable也设计为在另一个线程上运行。

另一方面,Supplier非常通用。它只是“提供一个值”而已。

因此,CallableSupplier更加专业化。如果您没有涉及到其他线程,或者您的任务很少可能抛出异常,则建议使用Supplier


14

细节

RunnableCallable自Java 6以来一直是并发包的一部分。这意味着它们都可以被提交给一个Executor异步运行。在这里,Callable有一个特定的用法。

Runnable(0输入0输出)、Supplier(0输入1输出)、Consumer(1输入0输出)和Function(1输入1输出)都是自Java 8以来函数特性的一部分。它们都可以被像CompletableFuture这样的lambda友好的东西处理。这里的Supplier只是表示没有任何输入参数但具有返回值的函数,高度抽象。

0个输入(参数) 1个输入(参数)
0个输出(返回值) Runnable Consumer
1个输出(返回值) Supplier Function

4
你的“进出”分析让我受益匪浅,谢谢。我添加了一个表格来可视化该分析。 - Basil Bourque

10

除了Callable抛出异常这一明显的区别外,两者的差异在于语义上。它们有不同的名称,因为它们代表着不同的事物。目的是使代码更易于理解。当您使用Callable时,您的接口选择意味着该对象将由另一个线程执行。当您使用Supplier时,您意味着它仅是向另一个组件提供数据的对象。


2
除了命名语义之外,在实现层面上,Callable 在多线程环境中是否有更好的选择?在多线程上下文中使用 Supplier 不安全吗? - arunkjn
3
没区别,它们只是接口。 - Torben

4

在我看来,Callable和Supplier的主要区别在于您更喜欢使用已检查的异常还是未检查的异常。

早期,Java生态系统通常会对预期遇到的每种情况都使用已检查的异常。但自从Java8之后,已检查的异常在流行度方面下降了。(例如:在异步任务执行期间包装异常时,ExecutionException与CompletionException通常都用于包装异常,但前者是已检查的,而后者则不是)

这甚至在一些库中也能看到(例如:Jackson的JSON解析会抛出已检查的异常,而Gson则会抛出未检查的异常等)。

不能说如果将在另一个线程中执行就使用Callable是正确的。例如,CompletableFuture.supplyAsync(supplier)会在另一个线程中执行提供程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接