Skip to content

五、异常处理(J45–J50) ​

J45 · 知识点:异常体系 ​

【题目】Java 中 Throwable 的直接分类是? A. 错误(Error)和异常(Exception) B. 受检异常(Checked)和非受检异常(Unchecked) C. IOException 和 SQLException D. 运行时异常和编译时异常,且两者编译时都必须捕获

答案:A

【考点】Throwable 顶层分类:Error 与 Exception 及其子分类。

【结论】选 A。Throwable 的直接子类仅 Error 与 Exception,二者构成 Java 异常体系最顶层二分。

【逐项辨析】

  • B错:受检/非受检是 Exception 的子分类,不是 Throwable 的直接分类。
  • A正确:Throwable 直接分为 Error(JVM 级错误)和 Exception(程序可处理异常)。
  • C错:IOException 与 SQLException 只是受检异常的具体例子,并非顶层分类。
  • D错:运行时异常与编译时异常并非标准术语,且 RuntimeException 编译期不强制捕获。

【知识点】 Java 异常体系以 Throwable 为根,直接派生 Error 与 Exception 两大分支。Error 表示 JVM 层面的严重问题(如 OutOfMemoryError、StackOverflowError),程序通常无法也不应捕获。Exception 表示程序运行中的可恢复异常,其下再分为:

分类继承关系编译期行为典型示例
受检异常Exception 且非 RuntimeException 子类强制 try-catch 或 throwsIOException、SQLException
非受检异常RuntimeException 及其子类不强制处理NullPointerException、ArrayIndexOutOfBoundsException

Error 与 Exception 同为 Throwable 的直接子类,这是异常体系中最顶层的划分。受检/非受检则是 Exception 之下的二级划分,不能替代 Error 与 Exception 的顶层地位。

【记忆锚点】 「Throwable 生了俩:Error 绝症,Exception 感冒;感冒还分看不看医生(Checked / Unchecked)。」

【易混对比】

  • 易混:题目问「Throwable 的直接分类」时,B 选项极具干扰性——受检/非受检属于 Exception 的二级分类,而非 Throwable 的直接子类(本题正确答案是 A:Error 与 Exception)。
  • 换问法:若问「编译期强制要求处理的异常属于哪一类?」则答案为 Checked Exception(受检异常)。

【自测】 以下哪个类不是 Throwable 的直接子类? A. Exception B. Error C. RuntimeException D. OutOfMemoryError

答:选 C(RuntimeException 挂在 Exception 之下,不是 Throwable 的直接子类)。注意 D 项「Error 与 Exception 同级」本身也成立——所以这道自测若照原样问会有两个正解;这里按「谁不是 Throwable 的直接子类」的口径答 C,实测 OutOfMemoryError.getSuperclass() = VirtualMachineError,Error 与 Exception 才是 Throwable 的直接子类。与第 J45 题连考。

【知识关联】

  • 同库关联:与 J46–J50 异常题群;Error vs Exception vs RuntimeException 边界。
  • 实现层:Throwable 子类;受检异常由编译器强制;运行时异常不强制捕获。
  • 面试追问:① 何时自定义受检异常?② Error 该不该捕获?

【拓展延伸】

  • 变式问法:ClassNotFoundException 与 NoClassDefFoundError 区别。
  • 版本差异:语义稳定;多异常捕获 JDK7+ 语法。
  • 工程注意点:不要吞异常;业务异常 vs 系统异常分层;保留 cause。

J46 · 知识点:finally 块 ​

【题目】关于 finally 块的说法,正确的是?

A. finally 块在任何情况下都会执行 B. 当 catch 或 try 中执行了 System.exit(0) 时,finally 不会执行 C. finally 中不能出现 return 语句 D. finally 中的 return 不会覆盖 try 中的 return

答案:B

【考点】finally 的执行时机与 System.exit 的例外。

【结论】选 B。finally 在正常情况下必定执行,但 System.exit(0) 直接终止 JVM 进程,finally 随之被跳过。

【逐项辨析】

  • A 错:finally 并非绝对执行,System.exit()、JVM 崩溃、线程被强制终止(kill -9)时均不执行。
  • B 正确:System.exit(0) 立即终止 JVM,finally 块不会得到执行机会。
  • C 错:finally 中不仅可以写 return,语法上完全合法。
  • D 错:finally 中的 return 会覆盖 try / catch 中的返回值,这是 Java 的明确规定,也是编码大忌。

【知识点】 finally 的设计目的是保证资源清理或收尾逻辑必定执行,其执行时机为 try 块正常结束、catch 块捕获异常后、或 return 语句真正返回之前。但存在以下例外场景导致 finally 不执行:

  1. 调用 System.exit(int) 或 Runtime.halt(int) 直接终止 JVM 进程;
  2. JVM 自身崩溃(如 SIGKILL 信号、操作系统级别杀进程);
  3. 执行 finally 的线程被中断或杀死。

此外,finally 中若包含 return,会抑制 try / catch 中的异常抛出,并覆盖先前的返回值,极易隐藏 Bug,因此编码规范(如《阿里巴巴 Java 开发手册》)明确禁止在 finally 中使用 return。

【记忆锚点】 「finally 不是万能保险,exit 杀进程全完蛋;finally 里别 return,覆盖了值还吞异常。」

【易混对比】

  • 易混:return 在 try 中并不会立即返回,而是先执行 finally;若 finally 无 return,再带回 try 中的返回值;若 finally 有 return,则 try / catch 的返回值和异常都被忽略。
  • 换问法:若问「以下哪种情况 finally 一定不执行?」应选 System.exit(0);若问「以下哪种写法会抑制异常?」应选 finally 中写 return。

【自测】 以下代码返回什么?

java
public static int test() {
    try {
        return 1;
    } finally {
        return 2;
    }
}

答:返回 2。finally 中的 return 覆盖了 try 中的 return,最终返回 2。与第 J46 题连考。

【知识关联】

  • 同库关联:与 J50(try-with-resources)、P63(Python 的 finally 与 return,同型陷阱)、面试高频「finally 是否执行」;J63 讲的是运行时数据区,不要错引成 finally。
  • 实现层:finally 在正常/异常/return 前都会插入;若 finally 也 return 会覆盖。
  • 面试追问:① finally 中 return 的陷阱?② System.exit 时 finally?

【拓展延伸】

  • 变式问法:try return 后 finally 改变量是否影响返回值(基本类型已计算)。
  • 版本差异:语义稳定。
  • 工程注意点:finally 只做清理不写 return;资源关闭用 try-with-resources。

J47 · 知识点:多 catch 顺序 ​

【题目】一个 try 后跟多个 catch 块时,正确的排列顺序是? A. 子类异常在前,父类异常在后 B. 父类异常在前,子类异常在后 C. 顺序无关紧要 D. 一个 try 只能有一个 catch

答案:A

【考点】catch 的匹配顺序与“不可达 catch”编译规则。

【结论】选 A。catch 按书写顺序自上而下匹配,子类异常必须放在父类异常之前,否则后面的 catch 将永远无法到达。

【逐项辨析】

  • A正确:先捕获子类(如 IOException),再捕获父类(如 Exception),保证每个 catch 都有可达路径。
  • B错:若父类在前,编译器会报「exception has already been caught」,子类 catch 不可达,编译失败。
  • C错:顺序至关重要,Java 编译器会检查 catch 块的可达性,父类写在前面时子类 catch 编译不过。
  • D错:一个 try 后可以跟多个 catch,这是 Java 异常处理的标准语法。

【知识点】 Java 编译器对 catch 块的检查遵循「可达性分析」规则。由于异常匹配采用「is-a」关系(即子类异常同样满足父类 catch 的捕获条件),一旦某个 catch 捕获了父类异常,其后的所有子类 catch 都将被判定为不可达代码(unreachable code),导致编译错误。

正确顺序示例:

java
try {
    // ...
} catch (FileNotFoundException e) {   // 子类在前
    // ...
} catch (IOException e) {             // 父类在后
    // ...
} catch (Exception e) {               // 更顶层父类
    // ...
}

同级异常(如 IOException 与 SQLException 互为兄弟类)之间顺序可以互换,因为不存在父子覆盖关系。

【记忆锚点】 「catch 排队先小后大,子在前父在后,反过来编译都不过。」

【易混对比】

  • 易混:catch(Exception e) 放在第一个位置是常见笔试陷阱,它会导致后面所有具体异常 catch 都编译报错。
  • 换问法:若问「以下代码能否编译通过?」并给出父类在前、子类在后的 catch 序列,答案应为「不能编译,子类 catch 不可达」。

【自测】 以下代码能否编译通过?

java
try {
    int a = 1 / 0;
} catch (Exception e) {
    System.out.println("Exception");
} catch (ArithmeticException e) {
    System.out.println("ArithmeticException");
}

答:不能编译通过。Exception 在前捕获了所有异常,后面的 ArithmeticException catch 不可达,编译报错。与第 J47 题连考。

【知识关联】

  • 同库关联:与 J45 体系、J49 自定义异常继承链。
  • 实现层:catch 按顺序匹配,父类 catch 会屏蔽后面的子类 catch(编译错误或不可达)。
  • 面试追问:① 多 catch 顺序规则?② JDK7 multi-catch 限制?

【拓展延伸】

  • 变式问法:先 catch Exception 后 catch IOException 能否编译。
  • 版本差异:JDK7 支持 catch (A | B e),不能有子父关系。
  • 工程注意点:先具体后宽泛;统一异常处理见 Spring S12。

J48 · 知识点:throw 与 throws ​

【题目】关于 throw 和 throws 的说法,正确的是?

A. throw 用于方法声明,throws 用于抛出异常对象 B. throw 在方法体内抛出异常对象,throws 在方法签名上声明可能抛出的异常类型 C. 两者可以互换使用 D. throws 用于创建异常对象

答案:B

【考点】throw(抛出实例)与 throws(声明类型)的区别。

【结论】选 B。throw 是在方法体内抛出异常实例的动作,throws 是在方法签名上声明可能抛出异常类型的语法。

【逐项辨析】

  • A错:throw 用于抛出实例,throws 用于方法声明,两者职责相反。
  • B正确:throw 后跟异常对象(如 throw new IOException());throws 后跟异常类型列表(如 throws IOException, SQLException)。
  • C错:二者语法位置、作用完全不同,绝对不能互换。
  • D错:throws 不创建对象,仅做类型声明;创建异常对象由 throw 配合 new 完成。

【知识点】 throw 与 throws 是 Java 异常处理机制的两个不同维度:

维度throwthrows
语法位置方法体内部方法签名尾部
作用实际产生并抛出一个异常对象声明方法可能抛出的异常类型
次数每次只能抛出一个实例可以声明多个异常类型,逗号分隔
强制性受检异常对象必须通过 throw 或内部调用产生受检异常必须声明或内部 try-catch 处理,否则编译失败

受检异常(Checked Exception)的传播链条:方法内抛出或内部方法传递来的受检异常,要么在当前方法内 try-catch 处理,要么通过 throws 继续向上抛给调用方处理,否则编译失败。

【记忆锚点】 「throw 是动手扔(new 对象),throws 是贴告示(声明类型);throw 在里,throws 在外。」

【易混对比】

  • 易混:throws 声明的异常类型必须与 throw 抛出的实例类型匹配(或为其实例的父类)。方法声明 throws Exception,实际可以 throw new IOException(),因为 IOException 是 Exception 的子类。
  • 换问法:若问「方法声明 throws IOException,但方法体内没有 throw 语句,是否合法?」答案是合法——因为 throws 也可以用来声明内部调用的其他方法可能传递过来的异常。

【自测】 以下方法声明是否有编译错误?

java
public void read() throws IOException {
    throw new FileNotFoundException("file missing");
}

答:无编译错误。FileNotFoundException 是 IOException 的子类,子类异常实例可以匹配父类 throws 声明。与第 J48 题连考。

【知识关联】

  • 同库关联:与 J45/J49;与 Spring @Transactional rollbackFor(S14)——checked 异常默认不回滚。
  • 实现层:throws 是方法签名声明;throw 是抛出动作;字节码 athrow。
  • 面试追问:① throws 与 throw 区别?② 重写方法 throws 规则?

【拓展延伸】

  • 变式问法:构造器 throws;接口方法异常声明。
  • 版本差异:语义稳定。
  • 工程注意点:API 文档写清受检异常;运行时异常优先用于编程错误。

J49 · 知识点:自定义异常 ​

【题目】若要定义自定义的受检异常(Checked Exception),应继承? A. RuntimeException B. Exception(且不是 RuntimeException 的子类) C. Error D. Throwable

答案:B

【考点】自定义异常的继承选择与受检/非受检的划分。

【结论】选 B。自定义受检异常应直接继承 Exception(且不属于 RuntimeException 体系),编译期才会强制调用方处理。

【逐项辨析】

  • A错:继承 RuntimeException 得到的是非受检异常,编译期不强制捕获或声明。
  • B正确:继承 Exception(非 RuntimeException 子类)即成为受检异常,符合题目要求。
  • C错:Error 代表 JVM 级严重错误,不应被应用程序继承用作业务异常。
  • D不选(并非功能错误):直接继承 Throwable 同样能编译,且抛出的自定义异常仍强制调用方 catch/throws——JDK 17 实测不处理会报「未报告的异常错误 MyEx2; 必须对其进行捕获或声明以便抛出」,按 JLS 它确实属于受检异常。问题在于它既不在 Exception 分支也不在 Error 分支(实测 Exception.class.isAssignableFrom(MyEx) 为 false),与框架、工具链按 Exception/Error 分类的约定冲突,语义不清。因此本题只取 B(继承 Exception)这一规范写法,D 是「能用但不该用」。

【知识点】 Java 异常的受检/非受检属性由继承链决定:

继承目标异常类型编译期行为典型用途
Exception(非 RuntimeException 子类)受检异常强制 try-catch 或 throws可恢复的外部错误(IO、SQL)
RuntimeException非受检异常不强制处理编程错误(空指针、越界)
Error错误不强制处理JVM 内部致命错误
Throwable根类不推荐使用一般不应直接继承

自定义异常的最佳实践:

  1. 受检异常 → 继承 Exception;
  2. 非受检异常 → 继承 RuntimeException(或 IllegalArgumentException 等已有子类);
  3. 提供带 String message 和 Throwable cause 的构造方法,便于异常链传递。

【记忆锚点】 「要受检就认 Exception 爹,不要 RuntimeException 这个干爹;认 Error 当爹是自寻死路。」

【易混对比】

  • 易混:RuntimeException 本身继承自 Exception,但 RuntimeException 及其所有子类都被 JVM 特殊对待,归为 Unchecked。因此「继承 Exception」不等于「受检」,必须是「继承 Exception 但不是 RuntimeException 的子类」才构成受检异常。
  • 换问法:若问「如何定义一个编译期不强制处理的自定义异常?」则应答「继承 RuntimeException」。

【自测】 以下自定义异常中,哪一个是受检异常?

java
class A extends Exception {}
class B extends RuntimeException {}
class C extends Throwable {}
class D extends Error {}

答:选 A(规范答案)。A 直接继承 Exception 且不在 RuntimeException 体系内,是标准受检异常;B 是非受检异常;D 属 Error 体系,不该用于业务异常。注意 C(直接继承 Throwable)在编译器看来同样必须 catch 或 throws,功能上也算受检异常,只是游离在 Exception/Error 两大分支之外,属于「能编译可用但不符合规范」的写法。与第 J49 题连考。

【知识关联】

  • 同库关联:与 J45/J48;工程上常做 BusinessException。
  • 实现层:继承 Exception(受检)或 RuntimeException(非受检)。
  • 面试追问:① 如何设计异常体系?② serialVersionUID 作用?

【拓展延伸】

  • 变式问法:自定义异常必须调用 super(message)?
  • 版本差异:语义稳定。
  • 工程注意点:携带错误码;不要用异常做流程控制;日志记录 cause。

J50 · 知识点:try-with-resources ​

【题目】JDK7 的 try-with-resources 语句要求资源类必须实现?

A. Serializable B. AutoCloseable(或其子接口 Closeable) C. Cloneable D. Runnable

答案:B

【考点】自动资源管理(ARM)的接口约束与关闭顺序。

【结论】选 B。try-with-resources 要求资源类实现 AutoCloseable 接口(或其子接口 Closeable),JVM 在退出 try 块时自动调用 close()。

【逐项辨析】

  • A 错:Serializable 是序列化标记接口,与资源关闭无关。
  • B 正确:AutoCloseable 是 try-with-resources 的强制接口约束,Closeable 继承自 AutoCloseable。
  • C 错:Cloneable 是对象克隆标记接口,与资源管理无关。
  • D 错:Runnable 是线程执行接口,与资源关闭无关。

【知识点】 try-with-resources 是 JDK7 引入的自动资源管理机制,语法为 try (Resource res = new Resource()) { ... }。其工作原理:

  1. 资源必须实现 AutoCloseable 接口(仅声明 void close() throws Exception);
  2. Closeable extends AutoCloseable,因此所有传统 IO 流(InputStream / OutputStream / Reader / Writer)均满足条件;
  3. try 块正常结束或发生异常退出时,JVM 自动按声明的逆序调用各资源的 close();
  4. 若 close() 也抛出异常,该异常会被抑制(suppressed),附加到主异常上,可通过 Throwable.getSuppressed() 获取。

多个资源声明示例:

java
try (BufferedReader br = new BufferedReader(new FileReader("a.txt"));
     BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"))) {
    // 使用 br、bw
} // 先关闭 bw,再关闭 br

【记忆锚点】 「try 后面加括号,AutoCloseable 来把关;先进后出逆序关,异常抑制挂旁边。」

【易混对比】

  • 易混:传统 try-finally 也能关闭资源,但 try-with-resources 更简洁且能处理 close() 异常 suppression。对比:若 finally 中 close() 抛异常,会覆盖 try 块的主异常;而 try-with-resources 会保留主异常,将 close 异常标记为 suppressed。
  • 换问法:若问「try-with-resources 中多个资源的关闭顺序是什么?」应答「与声明顺序相反(逆序关闭)」。

【自测】 以下代码中哪个类可以作为 try-with-resources 的资源?

java
class A implements AutoCloseable { public void close() {} }
class B implements Closeable { public void close() {} }
class C implements Runnable { public void run() {} }

答:A 和 B 都可以。A 直接实现 AutoCloseable;B 实现 Closeable(Closeable 继承 AutoCloseable);C 实现 Runnable,不满足条件。与第 J50 题连考。

【知识关联】

  • 同库关联:与 J46(finally)、J65/J63 资源与内存;与 P65(Python with)跨语言对照。
  • 实现层:JDK7+;AutoCloseable;编译为 try/finally 调用 close,抑制异常用 addSuppressed。
  • 面试追问:① 多个资源关闭顺序?② close 异常如何处理?

【拓展延伸】

  • 变式问法:括号内多个资源;自定义 AutoCloseable。
  • 版本差异:JDK9+ 允许 effectively final 已有资源变量复用。
  • 工程注意点:IO/连接一律 try-with-resources;不要手动 close 忘 finally。

持续学习,持续积累。