vector 的 get() 抛 arrayindexoutofboundsexception 是因继承 abstractlist 后直接访问底层数组,而数组越界在 java 中天然是 runtimeexception;concurrenthashmap 的 computeifabsent() 抛 nullpointerexception 是因映射函数返回 null,而非 key 为 null;completablefuture 使用 completionexception 透传原始异常,executionexception 则是 future.get() 的包装;现代并发工具统一用运行时异常以兼容函数式编程。

Vector 的 get() 方法为什么抛 ArrayIndexOutOfBoundsException 而不是检查型异常?
因为 Vector 继承自 AbstractList,其 get(int index) 直接委托给底层数组访问,而数组越界在 Java 中天然是运行时异常——ArrayIndexOutOfBoundsException 是 RuntimeException 子类,编译器不强制捕获。
这和早期设计哲学有关:JDK 1.0 时,集合类更倾向“快速失败”而非“防御性兜底”。你传错下标,程序立刻崩,而不是默默返回 null 或吞掉错误。
-
Vector的线程安全靠synchronized方法实现,但异常策略和ArrayList一致,不因同步而改变异常类型 - 别试图用
try-catch包裹每次get()调用——这是典型的“用异常控制流程”,性能差且掩盖逻辑缺陷 - 真正该做的是:调用前用
size()校验,或改用Optional.ofNullable(list.get(i)).orElse(...)(但注意Vector不支持流式操作)
ConcurrentHashMap 的 computeIfAbsent() 抛 NullPointerException 的真实原因
不是因为你传了 null key(它本来就不允许),而是你在计算函数里返回了 null。JDK 8+ 的 computeIfAbsent() 明确规定:映射函数不能返回 null,否则直接抛 NullPointerException,连包装都不包。
这个设计是为了避免歧义:如果允许返回 null,那到底是“没算出值”还是“算出来就是 null”?ConcurrentHashMap 选择一刀切——拒绝 null 值语义。
立即学习“Java免费学习笔记(深入)”;
- 常见错误:写
map.computeIfAbsent(key, k -> someMethodThatMightReturnNull()),结果一触发就崩 - 修复方式不是加
try-catch,而是确保 lambda 返回非null,比如k -> Optional.ofNullable(someMethod()).orElse(new DefaultObj()) - 注意 JDK 12+ 新增的
computeIfAbsent(key, mappingFunction, valueIfAbsent)重载,但仅限ConcurrentMap接口扩展,主流用的仍是 JDK 8 版本
CompletableFuture 异常链中 ExecutionException 和 CompletionException 怎么区分?
ExecutionException 来自 Future.get(),是老式线程池任务抛异常后的标准包装;CompletionException 才是 CompletableFuture 自己的“原生异常容器”,它会把原始异常作为 cause 透传,且不会多套一层。
这意味着:如果你用 thenApply() 后再调 join(),拿到的是 CompletionException;但若用 toCompletableFuture().get()(转成老接口),就会被包成 ExecutionException。
- 调试时别只看异常类名,重点看
getCause()——绝大多数业务异常都在里面 -
handle((r, t) -> {...})的t参数直接就是Throwable,可能是原始异常,也可能是CompletionException,取决于上游怎么抛 - 不要在
exceptionally()里重新 throw 新异常,否则会打断异常链,导致原始堆栈丢失
现代并发工具类为何几乎不用检查型异常?
因为检查型异常(Exception 及子类,非 RuntimeException)在异步、函数式、lambda 场景下根本没法声明。你不能在 Function<t></t> 接口里让 apply() 抛 IOException,除非自己定义新函数接口——这会破坏整个生态兼容性。
所以从 ConcurrentHashMap 到 CompletableFuture 再到 StructuredTaskScope(JDK 21),异常模型统一收束到运行时异常 + 明确文档约定。
- 比如
StructuredTaskScope的join()只抛InterruptedException,但实际任务异常全塞进getException(),由你手动处理 - 这种“异常延迟暴露”不是偷懒,而是为了不让异常声明污染函数签名,牺牲一点编译期检查,换来组合灵活性
- 真正难的不是选哪种异常,而是决定在哪一层展开、记录、重试或降级——这些逻辑不会出现在
catch块里,而在状态机或回调中
异常类型本身越来越不重要,重要的是异常发生时上下文是否可追溯、是否可恢复、是否被监控系统捕获。很多人盯着 throws 声明看半天,其实该盯的是日志里的 threadName 和 stackTrace 深度。








