面试中关于异常,主要考察 体系结构 和 执行流程陷阱 两大块。
1. 异常体系结构 (Exception Hierarchy)
一句话总结: 所有的异常都有一个共同的祖先 Throwable。
Throwable:所有错误和异常的超类。Error(错误): 系统级问题。- 通常是 JVM 出了问题(如内存溢出
OutOfMemoryError、栈溢出StackOverflowError)。 - 特点: 程序无法处理,一旦发生只能挂掉,不用 catch。
- 通常是 JVM 出了问题(如内存溢出
Exception(异常): 程序级问题。- 程序可以捕获并处理的问题。分为两大类:
- 受检异常 (Checked Exception):
- 编译期强制检查。如果不处理(try-catch 或 throws),代码编译不过。
- 例子:
IOException,SQLException,ClassNotFoundException。 - 场景: 外部不可控因素(文件不存在、网络断开)。
- 非受检异常 (Unchecked/Runtime Exception):
- 运行期异常。编译时不检查。
- 例子:
NullPointerException(空指针),IndexOutOfBoundsException(数组越界),ArithmeticException(/0)。 - 场景: 通常是代码逻辑写错了,应该修代码而不是 catch。
- 受检异常 (Checked Exception):
- 程序可以捕获并处理的问题。分为两大类:
2. 异常处理关键字
try:包裹可能出得代码。catch:捕获异常并处理。finally:无论是否发生异常,都一定会执行的代码块。通常用于释放资源(关闭 IO 流、数据库连接)。throw(动作):写在方法体内部。用于手动抛出一个异常对象。throw new RuntimeException("挂了");
throws(声明):写在方法签名上。声明该方法可能会抛出某些异常,交给调用者处理。public void readFile() throws IOException { ... }
3. 经典面试陷阱题
陷阱一:throw 和 throws 的区别?
- 位置不同:
throw在方法内;throws在方法名后面。 - 作用不同:
throw是真的扔出一个异常;throws只是告诉别人“这里可能会炸”,是一种风险预警。
陷阱二:try-catch-finally 的执行顺序(带 return) —— 必考
问题: 下面代码返回什么? Java
public String test() {
try {
return "a";
} finally {
return "b";
}
}
答案: 返回 “b”。 原理:
finally块的代码优先级极高。- 即使
try块里执行到了return,程序准备返回 “a” 之前,必须先去执行finally。 - 如果
finally里也有return,它会覆盖掉try里的返回值。
陷阱三:什么情况下 finally 不会执行?
只有一种极端情况:在 try 或 catch 中调用了 System.exit(0)(直接杀掉了 JVM 进程)。除此之外,finally 就算天塌下来也会执行。
4.为什么有些异常抛出了,却不需要在方法签名上写 throws 声明?
1. 核心结论
因为该异常属于 RuntimeException(非受检异常)。
-
强制要求: 只有
Checked Exception(受检异常,如IOException)必须在方法名后声明throws,否则编译器不让过。 -
非强制:
RuntimeException及其子类,你可以随心所欲地throw,编译器不会逼你写throws。
2. 深度逻辑:为什么要这么设计?
-
Checked Exception(必须要 throws):
-
性质: “预期内、可恢复”的外部故障。
-
场景: 比如网络突然断了、文件被删了。这些事很可能发生,调用者必须写好“备选方案”(try-catch)。
-
比喻: 就像登山领队(编译器)强制你必须带急救包,因为山上确实可能会有人擦伤,这是登山计划的一部分。
-
-
Runtime Exception(不用 throws):
-
性质: “不可恢复”的逻辑断层或环境灾难。
-
场景:
-
低级 Bug: 空指针、除以零。这是开发者逻辑不严密。
-
配置/环境崩坏(重点!): 比如你查数据库发现核心规则配置没了。代码没法跑了,必须立刻停下来(Fail-fast 机制)。
-
-
比喻: 就像你走路撞墙(Bug),或者脚下的路突然裂开了个大缝(数据库断了/配置没了)。这种时候戴头盔(try-catch)已经没用了,必须停下来修路,而不是假装没事继续走。
-
3. 为什么不强制声明 Runtime Exception?
-
代码整洁度: 如果空指针(NPE)也要强制
throws,那 Java 每一个方法后面都要跟上一长串throws,代码会变得极其臃肿。 -
解耦: 现代框架(如 Spring)倾向于把所有错误包装成
RuntimeException,这样中间层的代码就不必层层声明抛出,只需在最外层(全局异常处理器)统一抓取处理。
总结表格
| 类型 | 检查时机 | 开发者处理要求 | 代表人物 | 潜台词 |
|---|---|---|---|---|
| Error | 运行期 | 无法处理,直接原地爆炸 | OOM, StackOverflow | “别救了,程序断气了” |
| Checked | 编译期 | 必须 try-catch 或 throws | IOException, SQLException | “这事常发生,想好怎么收场” |
| Runtime | 运行期 | 不强制处理,通常建议抛出 | NullPointer, IllegalArgument | “要么代码有 Bug,要么环境炸了” |
“Checked Exception 是一种理想主义的设计,但在大型分布式架构中,它增加了系统的耦合度。现代实践更倾向于使用语义明确的 RuntimeException(非受检异常),结合全局异常处理器来实现解耦。”
