返回 CompletableFuture<Void> 还是 CompletableFuture<?>?

Posted

技术标签:

【中文标题】返回 CompletableFuture<Void> 还是 CompletableFuture<?>?【英文标题】:Return CompletableFuture<Void> or CompletableFuture<?>? 【发布时间】:2016-03-17 22:43:40 【问题描述】:

我想编写一个返回CompletableFuture 的异步方法。未来的唯一目的是跟踪方法何时完成,而不是其结果。返回CompletableFuture&lt;Void&gt;CompletableFuture&lt;?&gt; 会更好吗?是否有理由偏爱其中一个,或者它们可以互换?

CompletableFuture 本身从它的许多方法中返回 CompletableFuture&lt;Void&gt;java.nioAsynchronousSocketChannel 中有一个Future&lt;Void&gt;Future&lt;Void&gt; connect(SocketAddress remote)。 另一方面,java.util.concurrent 类如 ExecutorServiceScheduledExecutorService 返回 Future&lt;?&gt;:例如,Future&lt;?&gt; submit(Runnable task)

请注意,我只询问返回类型,而不是参数列表、变量声明或其他上下文。

【问题讨论】:

【参考方案1】:

最好使用CompletableFuture&lt;Void&gt;

Sotirios Delimanolis、Future&lt;?&gt; 发现的According to this answer 是一个小的 API 缺陷。在 Java 6 中,submit() 方法在内部使用了Future&lt;Object&gt;,因此它的返回类型设置为Future&lt;?&gt;。在 Java 7 中,实现更改为在内部使用 Future&lt;Void&gt;,但更改 API 为时已晚,因此返回值保持为 Future&lt;?&gt;

较新的 Java API 使用 Future&lt;Void&gt;CompletableFuture&lt;Void&gt;。这些是我们应该遵循的例子。

【讨论】:

【参考方案2】:

返回 CompletableFuture 会更好还是 可完成的未来>

是否有理由偏爱其中一个,或者他们是 可以互换吗?

代码可能会影响三种上下文:

运行时 - 泛型对它没有影响。 编译 - 我无法想象某些方法会接受 Future&lt;Void&gt; 但不会接受 Future&lt;?&gt; 的情况。 开发 - 如果Future 的结果没有意义,那么最好通过声明向用户说明这一点。

所以Future&lt;Void&gt; 更可取。

【讨论】:

【参考方案3】:

查看CompletableFuture API,您会发现CompletableFuture&lt;Void&gt; 与无法获得结果(因为它不存在)的副作用类方法一起使用,例如:

CompletableFuture.runAsync(Runnable runnable);

在这里返回CompletableFuture&lt;Object&gt; 会让人感到困惑,因为实际上没有结果,我们只关心完成。采用ConsumersRunnables 的方法返回CompletableFuture&lt;Void&gt;,例如:thenAcceptthenAcceptAsyncConsumerRunnable 通常用于副作用。

Void 的另一个用例是当您真的不知道结果时。例如:CompletableFuture.allOf,传递的列表可能是来自 Runnable 的 CompletableFuture,所以我们无法得到结果。

说了这么多,CompletableFuture&lt;Void&gt;只有在你没有其他选择的情况下才好,如果你能返回结果,那就去吧,如果调用者不感兴趣,他们可能会选择丢弃。您说您只对完成感兴趣,那么是的,CompletableFuture&lt;Void&gt; 会完成这项工作,但是如果您的 API 用户知道CompletableFuture&lt;T&gt; 是一个选项并且您只是代表他们决定他们永远不会需要结果。

【讨论】:

【参考方案4】:

合适的类型取决于它的语义。所有列出的选项都承诺发出完成信号,并可能异步返回异常。

CompletableFuture&lt;Void&gt;Void 告诉用户没有预期的结果。 CompletableFuture&lt;?&gt; ? 表示包含值的类型在可以传递任何值的意义上是未定义的。

CompletableFuture 类从CompletionStage 继承了几个便利方法。但它也允许您的方法的调用者触发未来的完成,这似乎是错误的,因为您的方法负责自己发出信号完成。还有一个cancel(...) 方法在CompletableFuture 的默认实现中毫无意义,因为它不会取消执行。

Future&lt;Void&gt;Void 告诉用户没有预期的结果。 Future&lt;?&gt; ? 表示包含值的类型在可以传递任何值的意义上是未定义的。

Future 缺少来自CompletionStage 的便捷方法。它不允许触发未来的完成,但可以取消执行。

下一个选项是CompletionStage&lt;Void&gt;:

CompletionStage&lt;Void&gt;Void 告诉用户没有预期的结果。提供了绑定处理程序的便捷方法,但没有 cancel(...) 方法。您的方法的调用者无法触发 CompletionStage 的完成。 &lt;CancellableFuture extends Future&lt;Void&gt; &amp; CompletionStage&lt;Void&gt;&gt;:来自Future&lt;Void&gt;CompletionStage&lt;Void&gt; 的方法集。它表明没有结果,存在便利方法以及取消选项。您的方法的调用者无法触发 CompletionStage 的完成。

cancel(...) 方法的缺失可能适合您的场景,也可能不适合。因此,如果您不需要取消,我建议使用CompletionStage&lt;Void&gt;,如果您需要取消执行的选项,请使用&lt;CancellableFuture extends Future&lt;Void&gt; &amp; CompletionStage&lt;Void&gt;&gt;。如果您选择了&lt;CancellableFuture extends Future&lt;Void&gt; &amp; CompletionStage&lt;Void&gt;&gt;,您可能希望自己创建一个继承自Future&lt;Void&gt;CompletionStage&lt;Void&gt; 的接口用作返回类型,而不是将长类型交集直接放在您的方法声明中。

您应该避免使用声明的返回类型 CompletableFuture 返回,因为调用者可能会触发未来的完成。这样做会故意导致混乱的代码和令人惊讶的挂起,因为现在还不清楚哪个代码负责触发完成。使用上述更受限制的类型之一,让类型系统防止您的方法调用者意外触发完成。

【讨论】:

以上是关于返回 CompletableFuture<Void> 还是 CompletableFuture<?>?的主要内容,如果未能解决你的问题,请参考以下文章

如何完成 CompletableFuture<Void>?

thenApply()和thenCompose()的区别

java8CompletableFuture常用的异步操作

线程异步编排串行(CompletableFuture)

线程异步编排串行(CompletableFuture)

CompletableFutureCompletableFuture测试runAsync()方法调用CompletableFuture.join()/get()方法阻塞主线程