1. 引言

在 Java 并发编程中,CompletableFuture 是一个强大的工具,能让我们编写非阻塞代码。使用 CompletableFuture 时,会遇到两个常用方法:join()get()。两者都用于获取计算完成后的结果,但存在关键差异。

本文将深入探讨这两个方法的区别,帮你避免踩坑。

2. CompletableFuture 概述

在比较 join()get() 前,先快速回顾 CompletableFuture 是什么。它代表异步计算的未来结果,相比传统回调方式,能更清晰地编写异步代码。看个简单例子:

首先创建 CompletableFuture

CompletableFuture<String> future = new CompletableFuture<>();

然后完成计算并设置值:

future.complete("Hello, World!");

最后通过 join()get() 获取结果:

String result = future.join(); // 或 future.get();
System.out.println(result); // 输出: Hello, World!

3. join() 方法详解

join() 是获取 CompletableFuture 结果的简单粗暴方式。它会等待计算完成并返回结果。如果计算抛出异常,join() 会抛出未受检异常CompletionException)。

方法签名:

public T join()

核心特点:

  • ✅ 计算完成后返回结果
  • ❌ 计算异常时抛出 CompletionException(未受检异常)
  • ⚠️ 无需显式处理或声明异常,代码更简洁

4. get() 方法详解

get() 方法也能获取计算结果,但错误时抛出受检异常。它有两个变体:无限等待和超时版本。

方法签名:

public T get() throws InterruptedException, ExecutionException
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException

核心特点:

  • ✅ 计算完成后返回结果
  • ❌ 可能抛出 InterruptedExceptionExecutionExceptionTimeoutException(均为受检异常)
  • ⚠️ 必须显式处理或声明异常,代码稍显繁琐

历史背景get() 继承自 Future 接口(Java 5 引入)。Java 8 新增 CompletableFuture 时为保持向后兼容,保留了 get() 方法。

5. join() vs get() 对比

对比维度 join() get()
异常类型 CompletionException(未受检) InterruptedException 等受检异常
异常处理 无需声明或捕获 必须声明或捕获
超时支持 ❌ 不支持 ✅ 支持(带参数版本)
来源 CompletableFuture 专属方法 继承自 Future 接口
推荐场景 新代码首选 仅用于兼容旧代码

简单总结:

  • 新代码优先用 join():异常处理更简单
  • 需要超时控制或兼容旧代码时用 get()

6. 测试验证

通过测试用例加深理解:

@Test
public void givenJoinMethod_whenThrow_thenGetUncheckedException() {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Test join");

    assertEquals("Test join", future.join());

    CompletableFuture<String> exceptionFuture = CompletableFuture.failedFuture(new RuntimeException("Test join exception"));

    assertThrows(CompletionException.class, exceptionFuture::join);
}

@Test
public void givenGetMethod_whenThrow_thenGetCheckedException() {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Test get");

    try {
        assertEquals("Test get", future.get());
    } catch (InterruptedException | ExecutionException e) {
        fail("Exception should not be thrown");
    }

    CompletableFuture<String> exceptionFuture = CompletableFuture.failedFuture(new RuntimeException("Test get exception"));

    assertThrows(ExecutionException.class, exceptionFuture::get);
}

7. 结论

join()get() 都能获取 CompletableFuture 的结果,但异常处理方式不同:

  • join() 抛出未受检异常,适合不想显式处理异常的场景
  • get() 抛出受检异常,提供更精细的异常控制和超时支持

实践建议

  • ✅ 新代码优先用 join(),简洁高效
  • ⚠️ 需要超时控制或兼容旧代码时才用 get()

完整示例代码可在 GitHub 查看。


原始标题:Guide to CompletableFuture join() vs get() | Baeldung