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
核心特点:
- ✅ 计算完成后返回结果
- ❌ 可能抛出
InterruptedException
、ExecutionException
或TimeoutException
(均为受检异常) - ⚠️ 必须显式处理或声明异常,代码稍显繁琐
历史背景:
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 查看。