join()不抛受检异常,编译无需处理;get()强制捕获interruptedexception和executionexception;join()仅抛completionexception、cancellationexception等运行时异常;get()支持超时等待,join()不支持。

CompletableFuture 的 join() 和 get() 都会阻塞当前线程等待结果,但它们在异常处理机制上存在根本性差异——核心就落在“受检异常”(checked exception)是否强制要求调用方处理。
get() 强制处理 InterruptedException 和 ExecutionException
get() 继承自 Future 接口,方法签名明确声明抛出两个受检异常:
-
InterruptedException:当前线程在等待过程中被中断时抛出。Java 要求你必须 显式捕获或向上声明,否则编译不通过。 -
ExecutionException:异步任务执行中抛出的原始异常会被封装进它里面。这也属于受检异常,无法忽略。
这意味着使用 get() 时,编译器会强制你写 try-catch 或 throws,哪怕你只是想简单取个值。
join() 只抛运行时异常,不触发编译检查
join() 是 CompletableFuture 特有的方法,它只抛出 CompletionException(继承自 RuntimeException)和 CancellationException(也是运行时异常)。
- 这两者都属于 非受检异常(unchecked exception),编译器不会强制你捕获或声明。
- 如果任务内部抛了
NullPointerException或ArithmeticException,join()会原样包一层CompletionException后抛出,但调用端可以完全不写 try-catch —— 编译照样通过。
异常链结构不同,影响错误定位
虽然两者都会暴露原始异常,但包装方式影响调试体验:
-
get()抛出ExecutionException,需调用e.getCause()才能拿到业务异常; -
join()抛出CompletionException,同样要靠e.getCause()查原始原因,但堆栈里通常更贴近真实执行位置,尤其在函数式链式调用中更干净。
要不要超时?这是 get() 独占的能力
get(long timeout, TimeUnit unit) 支持设置等待上限,超时直接抛 TimeoutException(也是受检异常);而 join() 没有超时重载,只能无限等待或手动配合 orTimeout() 等组合操作实现类似效果。










