异常体系与分类
面试官问:"Java 的异常体系结构是什么?"
候选人小吴答:"有 Exception 和 Error,Exception 又分为受检异常和非受检异常。"
面试官点点头:"受检异常和非受检异常有什么区别?"
小吴:"受检异常必须显式处理,非受检异常不用。"
面试官追问:"为什么要这样设计?受检异常有什么缺点?"
小吴停顿了很久,说不出来。
【面试官心理】 这道题考的不是记忆力,是对 Java 语言设计哲学的理解。能说出受检异常设计争议的候选人,是真正思考过这个问题的,直接加分。
一、异常体系结构 🔴
二、Error vs Exception 🔴
不要捕获 Error 或 Throwable(除非是框架层面的 catch-all 处理)。Error 发生时 JVM 状态已经不可信,捕获后继续运行可能产生更严重的问题。
三、受检异常 vs 非受检异常 🔴
3.1 定义
受检异常(Checked Exception):继承 Exception 但不继承 RuntimeException,编译器强制要求处理(try-catch 或 throws 声明)。
非受检异常(Unchecked Exception):继承 RuntimeException,编译器不强制要求处理。
3.2 受检异常的设计争议
支持受检异常的观点:
- 强制调用方意识到异常情况,提高代码健壮性
- 接口契约的一部分:调用方知道需要处理什么异常
反对受检异常的观点(更多人支持):
- 代码冗余:很多场景只能向上抛,产生大量样板代码
- 封装泄漏:接口强制依赖实现细节(如底层用了 JDBC 就要在接口 throws SQLException)
- 链式传播:异常需要一层层显式 throws,修改底层实现会影响所有上层
Kotlin、C#、Swift 等现代语言都选择不支持受检异常,说明受检异常在实践中争议较大。
能说出受检异常的设计争议,以及现代语言趋势的候选人,面试官会认为有语言设计思考能力,P6/P7 加分点。
四、异常处理最佳实践 🔴
4.1 不要吞掉异常
4.2 不要捕获 Exception/Throwable
4.3 异常链不要丢失
4.4 finally 块中不要抛出异常
五、自定义异常规范
生产中推荐:业务异常继承 RuntimeException(非受检),统一错误码,全局 ExceptionHandler 处理。能说出这套最佳实践的候选人,面试官会认为有工程经验。
六、追问升级
面试官:"try-catch 有性能开销吗?"
回答:正常执行路径(没有异常抛出)几乎没有性能开销。只有异常真正被抛出时,才需要收集堆栈信息,这个开销较大(涉及遍历调用栈)。
所以:不要用异常做流程控制,比如用异常判断 key 是否存在。
【面试官心理】 问"异常的性能开销",是在测候选人有没有生产意识。用异常做流程控制是初级程序员的常见错误,能说出这个问题并给出正确姿势的候选人,基本确认有一定的工程积累。