返回首页

Java 异常处理学习笔记

439 3 分钟2026-08-17编程开发 › Java 基础#Java#Exception Handling#Best Practices#Interview#Backend

Java 异常体系学习笔记:Throwable 继承树、Checked 与 Unchecked 区别、try-catch-finally 执行顺序、try-with-resources、自定义异常设计与高频面试题,附记忆口诀。

一、异常整体概念与分类

被问到异常处理,先说整体概念分类(Error / Exception,Checked / Unchecked)。

Throwable 继承体系

Throwable (所有错误和异常的祖先)
│
├── Error (严重错误, 程序通常无法恢复, 不需要捕获处理)
│    ├── OutOfMemoryError
│    ├── StackOverflowError
│    └── ... (虚拟机错误、系统资源耗尽等)
│
└── Exception (程序可处理的异常)
     │
     ├── Checked Exception (检查型异常/编译时异常)
     │    ├── IOException (文件操作)
     │    ├── FileNotFoundException
     │    ├── ClassNotFoundException
     │    ├── SQLException
     │    └── ... (其他需要在编译时处理的异常)
     │
     └── Unchecked Exception (RuntimeException, 非检查型异常/运行时异常)
          ├── NullPointerException (NPE - 面试最爱考!)
          ├── ArrayIndexOutOfBoundsException (数组越界)
          ├── ClassCastException (类型转换错误)
          ├── IllegalArgumentException (非法参数)
          ├── ArithmeticException (算术异常, 如除以 0)
          └── ... (其他由程序逻辑错误导致的异常)

Exception 和 Error 的区别

  • Exception: 程序本身可以预见的、可以捕获处理的异常情况。代表了程序可以恢复的问题。
  • Error: 非常严重的、通常由 JVM 或系统底层资源导致的错误(如内存溢出、栈溢出、虚拟机崩溃)。程序不应该尝试捕获这些错误,因为它们通常意味着程序已无法正常运行。

Checked vs Unchecked (Runtime) 异常

这是重中之重——编译器如何对待它们

  • Checked(检查型): Exception 中除 RuntimeException 及其子类之外的部分。
    • 特点: 编译器强制检查和处理。如果方法里可能抛出或内部方法抛出了检查型异常,你必须:
      • 要么用 try-catch 捕获并处理掉它;
      • 要么在方法签名上用 throws 声明会抛出此异常(把"锅"甩给调用者)。
  • Unchecked / Runtime(运行时异常): ErrorRuntimeException 及其子类。
    • 特点: 编译器不强制要求处理(可以不用 try-catchthrows)。通常由程序逻辑错误导致(如空指针、数组越界、错误的类型转换、无效参数等)。
    • 面试常见题: NullPointerExceptionArrayIndexOutOfBoundsException 为什么不强制要求 try-catch

记忆口诀: Error 真崩溃,Exception 可应对。Exception 分两种,Checked 编译要声明,Runtime 不必提。检查要动手(处理或声明),运行靠逻辑。

二、核心概念与关键词(必背!)

try

标记可能抛出异常的代码块(危险区)。

catch

捕获并处理特定类型的异常。可以有多个 catch 块。

  • 面试重点: catch 块顺序很重要!必须先捕获子类异常,再捕获父类异常。如果父类在子类前面,子类异常永远捕获不到。
  • JDK 7+ 多重捕获: catch (IOException | SQLException e) { ... } 可以用 | 捕获多个不相关的异常类型。

finally

无论是否发生异常,无论是否被捕获,该块内的代码都会执行!

  • 核心用途: 释放资源(关闭文件流、数据库连接、网络套接字等),确保资源不被泄露。
  • 面试重点 1: finallyreturn 的执行顺序?
    • 如果 trycatch 中有 return,会先执行 return 语句的计算(将返回值放入栈),然后执行 finally 块,最后才真正结束方法并返回之前计算好的值。
    • 如果 finally 里也有 return,会覆盖掉 trycatch 中的返回值!(危险!应避免这样写!)

throw vs throws

  • throws 用在方法签名中,声明该方法可能会抛出哪些检查型异常(Checked Exception)
    • 告诉调用者:"我这个方法可能会出 XXX 问题,请你做好准备(要么 try-catch 处理我,要么你也 throws 往上抛)"。
    • 例:public void readFile() throws IOException { ... }
  • throw方法内部,主动抛出一个异常对象。
    • 例:throw new NullPointerException("参数不能为空!");

三、try-with-resources(JDK 7+)

  • 用来自动关闭实现了 AutoCloseable 接口的资源(如流、连接)。
  • 语法:try (资源声明1; 资源声明2) { ... } 不需要显式调用 close(),也不用手写 finally 关闭,编译器会生成正确的关闭逻辑。
  • 优势: 简洁、安全、避免资源泄露。
  • 面试高频: 对比传统 try-catch-finallytry-with-resources,说出好处。
// 编译器自动生成关闭逻辑,安全且简洁
try (FileInputStream fis = new FileInputStream("file.txt")) {
    // 业务逻辑
} catch (IOException e) {
    e.printStackTrace();
}

四、最佳实践与常见问题

1. 自定义异常

  • 为什么用? 创建特定于业务逻辑或领域的错误类型,提高代码可读性和可维护性。
  • 如何做? 继承 Exception(创建检查型异常)或 RuntimeException(创建非检查型异常)。
  • 要点:
    • 提供无参构造器。
    • 提供带 String message 的构造器(传递错误信息)。
    • 提供带 Throwable cause 的构造器(保留原始异常信息)。
    • 通常重写 toString()getMessage() 提供更友好信息。
  • 面试题: 如何设计一个自定义异常?

2. 异常处理的性能开销

  • 创建异常对象(包含栈轨迹)有开销。
  • try 块本身开销很小。
  • 核心原则: 不要用异常来控制正常流程逻辑(尤其是非检查型异常)! 异常只该用于处理异常情况。用 if 检查预防 NullPointerException 比捕获它高效得多!

3. 为什么异常信息会丢失或被"吞掉"?

  • 错误示例 1: catch 块里只写了 e.printStackTrace(); 但没有后续处理或再次抛出
  • 错误示例 2: catch 块捕获了一个异常 A,在处理过程中又抛出一个异常 B,但没有把 A 作为 B 的 cause(new MyException("...", e)),导致异常链丢失。
  • 错误示例 3: finally 块里抛出的异常会覆盖掉 trycatch 块里的异常(如果前者没有被捕获)。

五、高频面试题速查

  1. finally 块的作用是?
    确保资源释放(如 FileInputStream.close())。
  2. try-catch-finally 块中,如果 tryreturn 了,finally 还执行吗?
    执行! 它会在 return 语句计算后、方法真正返回前执行。
  3. 如果 finally 里也有 return 会怎样?
    会覆盖 trycatch 中的返回值!(危险!避免这样写!)
  4. 异常处理的性能与最佳实践?
    finally 关资源,try-resources 更简单,别吞异常、别滥用异常控制流程。
  5. 声明与抛出?
    throw 抛出动作,throws 声明风险。

快速总结口诀

结构: Throwable,Error 和 Exception 分两边;Exception 两兄弟,Checked 要管,Runtime 靠自律。

块: try 标记危险区,catch 处理抓异常,多 catch 子类先,finally 必执行清资源。

检查与非检查: 检查异常要管(处理或声明),运行时靠逻辑(防 NPE、防越界)。

正在初始化...
Java 异常处理学习笔记 | WitchCat