编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

    • README
    • 入门教程

      • README
      • 基础语法
      • 数据类型
      • 运算符
      • 字符串和数组
      • 流程语句
      • 函数方法
      • 类和对象
      • 继承和多态
      • 接口和抽象类
      • 异常处理
        • 10.1 异常入门介绍
          • 10.1.1 异常概念解释
          • 10.1.2 异常体系结构
          • 10.1.3 异常基本语法
          • 10.1.4 捕获异常
          • 10.1.5 综合案例:异常信息分析器
          • 10.1.6 异常入门训练题
        • 10.2 多级catch匹配
          • 10.2.1 多级catch使用
          • 10.2.2 多异常捕获(JDK7+)
          • 10.2.3 综合案例:输入安全解析器
          • 10.2.4 多级catch训练题
        • 10.3 finally语句
          • 10.3.1 finally基本用法
          • 10.3.2 try-with-resources(JDK7+)
          • 10.3.3 综合案例:资源管理对比实验
          • 10.3.4 finally和try-with-resources训练题
        • 10.4 throw和throws
          • 10.4.1 throw抛出异常
          • 10.4.2 throws声明异常
          • 10.4.3 综合案例:参数校验工具
          • 10.4.4 throw和throws训练题
        • 10.5 自定义异常
          • 10.5.1 自定义异常类
          • 10.5.2 自定义异常实践
          • 10.5.3 综合案例:订单异常体系
          • 10.5.4 自定义异常训练题
        • 10.6 常见异常汇总
          • 10.6.1 综合案例:异常速查手册
          • 10.6.2 常见异常训练题
      • 集合框架
      • IO流和File
      • 线程和锁
      • 泛型
      • 注解和反射
    • 综合案例

    • 专栏博客

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Java入门精通
  • 入门教程
杨充
2026-04-07
目录

异常处理

# 10.异常处理

# 目录介绍

  • 10.1 异常入门介绍
    • 10.1.1 异常概念解释
    • 10.1.2 异常体系结构
    • 10.1.3 异常基本语法
    • 10.1.4 捕获异常
    • 10.1.5 综合案例:异常信息分析器
    • 10.1.6 异常入门训练题
  • 10.2 多级catch匹配
    • 10.2.1 多级catch使用
    • 10.2.2 多异常捕获(JDK7+)
    • 10.2.3 综合案例:输入安全解析器
    • 10.2.4 多级catch训练题
  • 10.3 finally语句
    • 10.3.1 finally基本用法
    • 10.3.2 try-with-resources(JDK7+)
    • 10.3.3 综合案例:资源管理对比实验
    • 10.3.4 finally和try-with-resources训练题
  • 10.4 throw和throws
    • 10.4.1 throw抛出异常
    • 10.4.2 throws声明异常
    • 10.4.3 综合案例:参数校验工具
    • 10.4.4 throw和throws训练题
  • 10.5 自定义异常
    • 10.5.1 自定义异常类
    • 10.5.2 自定义异常实践
    • 10.5.3 综合案例:订单异常体系
    • 10.5.4 自定义异常训练题
  • 10.6 常见异常汇总
    • 10.6.1 综合案例:异常速查手册
    • 10.6.2 常见异常训练题

# 10.1 异常入门介绍

# 10.1.1 异常概念解释

程序的错误大致可以分为三种:

  1. 语法错误:编译阶段就能发现,如拼写错误、缺少分号等。
  2. 逻辑错误:程序能运行但结果不对,通过调试解决。
  3. 运行时错误:程序运行期间发生的错误,如除数为0、空指针、数组越界等。

Java 通过异常机制处理运行时错误,让程序有机会优雅地处理错误,而不是直接崩溃。

异常处理的底层原理:Java 的异常处理在字节码中通过异常表(Exception Table) 实现,而不是像某些语言使用 setjmp/longjmp。每个方法的 Code 属性中都包含一个异常表,记录了 try 块的起始/结束 PC、catch 处理器的入口 PC 和捕获的异常类型。当异常抛出时,JVM 在当前方法的异常表中查找匹配项;若未找到,则弹出当前栈帧,沿调用栈向上查找(栈展开),直到找到匹配的 handler 或到达栈顶(线程终止)。throw new Exception() 的开销主要来自填充异常栈轨迹(fillInStackTrace()),需要遍历整个调用栈——这就是为什么不应该用异常控制正常业务流程。

对比 C++:C++ 也有 try-catch 异常机制,但 C++ 没有"检查异常"的概念,异常处理不是强制的。Java 的异常处理更加规范和严格。

# 10.1.2 异常体系结构

Throwable
├── Error(严重错误,程序无法处理)
│   ├── OutOfMemoryError(内存溢出)
│   ├── StackOverflowError(栈溢出)
│   └── ...
└── Exception(异常,程序可以处理)
    ├── RuntimeException(运行时异常/非检查异常)
    │   ├── NullPointerException(空指针)
    │   ├── ArrayIndexOutOfBoundsException(数组越界)
    │   ├── ArithmeticException(算术异常)
    │   ├── ClassCastException(类型转换异常)
    │   └── ...
    └── 检查异常(编译时必须处理)
        ├── IOException(IO异常)
        ├── SQLException(数据库异常)
        ├── FileNotFoundException(文件未找到)
        └── ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

关键区别:

  • 检查异常:编译器强制要求处理(try-catch 或 throws),如 IOException。
  • 运行时异常:编译器不强制要求处理,如 NullPointerException。

# 10.1.3 异常基本语法

try {
    // 可能会抛出异常的代码
} catch (异常类型 变量名) {
    // 异常处理代码
} finally {
    // 无论是否异常都会执行(可选)
}
1
2
3
4
5
6
7

# 10.1.4 捕获异常

public class Main {
    public static void main(String[] args) {
        try {
            int result = 10 / 0;  // ArithmeticException
            System.out.println(result);
        } catch (ArithmeticException e) {
            System.out.println("发生算术异常:" + e.getMessage());
        }
        System.out.println("程序继续执行");
    }
}
// 输出:
// 发生算术异常:/ by zero
// 程序继续执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14

异常对象的常用方法:

try {
    int[] arr = null;
    System.out.println(arr[0]);
} catch (NullPointerException e) {
    // getMessage():获取异常消息
    System.out.println("消息:" + e.getMessage());
    
    // toString():获取异常类型 + 消息
    System.out.println("描述:" + e.toString());
    
    // printStackTrace():打印完整的调用栈轨迹(调试用)
    e.printStackTrace();
    
    // getStackTrace():以数组形式获取调用栈
    StackTraceElement[] stack = e.getStackTrace();
    for (StackTraceElement element : stack) {
        System.out.println("  at " + element);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 10.1.5 综合案例:异常信息分析器

本案例综合运用异常概念、异常体系、try-catch 基本语法、异常对象方法。

/**
 * 异常信息分析器 —— 异常入门综合案例
 * 知识点:异常体系、try-catch、getMessage/toString/printStackTrace/getStackTrace
 */
public class ExceptionAnalyzer {

    // 模拟多层调用链(制造有深度的栈轨迹)
    static void methodA() {
        methodB();
    }

    static void methodB() {
        methodC();
    }

    static void methodC() {
        throw new ArithmeticException("除数不能为零");
    }

    // 分析并打印异常的详细信息
    static void analyzeException(Exception e) {
        System.out.println("  getMessage()  : " + e.getMessage());
        System.out.println("  toString()    : " + e.toString());
        System.out.println("  异常类型       : " + e.getClass().getSimpleName());

        // getStackTrace() 获取调用栈
        StackTraceElement[] stack = e.getStackTrace();
        System.out.println("  调用栈深度     : " + stack.length);
        for (int i = 0; i < Math.min(stack.length, 3); i++) {
            System.out.printf("    [%d] %s.%s (行%d)%n",
                    i, stack[i].getClassName(), stack[i].getMethodName(),
                    stack[i].getLineNumber());
        }
    }

    public static void main(String[] args) {
        System.out.println("===== 异常信息分析器 =====\n");

        // 1. 运行时异常(RuntimeException子类)
        System.out.println("--- 测试1: ArithmeticException(调用链传播)---");
        try {
            methodA();  // A → B → C,在C中抛出异常
        } catch (ArithmeticException e) {
            analyzeException(e);
        }

        // 2. 空指针异常
        System.out.println("\n--- 测试2: NullPointerException ---");
        try {
            String str = null;
            str.length();  // 空指针
        } catch (NullPointerException e) {
            analyzeException(e);
        }

        // 3. 数组越界
        System.out.println("\n--- 测试3: ArrayIndexOutOfBoundsException ---");
        try {
            int[] arr = {1, 2, 3};
            int val = arr[10];
        } catch (ArrayIndexOutOfBoundsException e) {
            analyzeException(e);
        }

        // 4. 类型转换异常
        System.out.println("\n--- 测试4: ClassCastException ---");
        try {
            Object obj = "hello";
            Integer num = (Integer) obj;
        } catch (ClassCastException e) {
            analyzeException(e);
        }

        // 5. 异常后程序继续执行
        System.out.println("\n--- 测试5: 异常不会终止程序 ---");
        System.out.println("  try-catch 处理后程序正常继续 ✓");

        // 6. 异常体系关系验证
        System.out.println("\n--- 异常体系关系验证 ---");
        ArithmeticException ae = new ArithmeticException();
        System.out.println("  ArithmeticException instanceof RuntimeException : "
                + (ae instanceof RuntimeException));
        System.out.println("  ArithmeticException instanceof Exception        : "
                + (ae instanceof Exception));
        System.out.println("  ArithmeticException instanceof Throwable        : "
                + (ae instanceof Throwable));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

运行结果:

===== 异常信息分析器 =====

--- 测试1: ArithmeticException(调用链传播)---
  getMessage()  : 除数不能为零
  toString()    : java.lang.ArithmeticException: 除数不能为零
  异常类型       : ArithmeticException
  调用栈深度     : 4
    [0] ExceptionAnalyzer.methodC (行16)
    [1] ExceptionAnalyzer.methodB (行12)
    [2] ExceptionAnalyzer.methodA (行8)

--- 测试2: NullPointerException ---
  getMessage()  : null
  toString()    : java.lang.NullPointerException
  异常类型       : NullPointerException
  调用栈深度     : 1
    [0] ExceptionAnalyzer.main (行34)

--- 测试3: ArrayIndexOutOfBoundsException ---
  getMessage()  : Index 10 out of bounds for length 3
  异常类型       : ArrayIndexOutOfBoundsException

--- 测试4: ClassCastException ---
  getMessage()  : class java.lang.String cannot be cast to class java.lang.Integer
  异常类型       : ClassCastException

--- 测试5: 异常不会终止程序 ---
  try-catch 处理后程序正常继续 ✓

--- 异常体系关系验证 ---
  ArithmeticException instanceof RuntimeException : true
  ArithmeticException instanceof Exception        : true
  ArithmeticException instanceof Throwable        : true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 10.1.6 异常入门训练题

训练1:编写一个方法 int divide(int a, int b),当 b 为 0 时抛出异常。在 main 方法中调用并捕获异常,打印友好的错误信息。

训练2:编写程序,让用户输入一个整数。使用 try-catch 处理用户输入非数字字符串的情况(NumberFormatException)。

思考:以下代码中 System.out.println("B") 会执行吗?为什么?

try {
    System.out.println("A");
    int r = 10 / 0;
    System.out.println("B");
} catch (ArithmeticException e) {
    System.out.println("C");
}
System.out.println("D");
1
2
3
4
5
6
7
8

# 10.2 多级catch匹配

# 10.2.1 多级catch使用

try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[5]);       // ArrayIndexOutOfBoundsException
    System.out.println(10 / 0);       // ArithmeticException
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("数组越界:" + e.getMessage());
} catch (ArithmeticException e) {
    System.out.println("算术异常:" + e.getMessage());
} catch (Exception e) {
    System.out.println("其他异常:" + e.getMessage());
}
1
2
3
4
5
6
7
8
9
10
11

注意:catch 的顺序必须从子类到父类,否则编译错误。

# 10.2.2 多异常捕获(JDK7+)

try {
    // ...
} catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
    System.out.println("发生异常:" + e.getMessage());
}
1
2
3
4
5

# 10.2.3 综合案例:输入安全解析器

本案例综合运用多级 catch 匹配和 JDK7+ 多异常捕获语法。

/**
 * 输入安全解析器 —— 多级catch综合案例
 * 知识点:多级catch从子类到父类、JDK7+多异常捕获、catch顺序
 */
public class SafeParser {

    // 安全解析整数
    static int safeParseInt(String input) {
        try {
            if (input == null) {
                throw new NullPointerException("输入为null");
            }
            return Integer.parseInt(input.trim());
        } catch (NumberFormatException e) {
            System.out.println("    格式错误:\"" + input + "\" 不是有效整数");
            return 0;
        } catch (NullPointerException e) {
            System.out.println("    空值错误:" + e.getMessage());
            return 0;
        }
    }

    // 安全数组访问
    static String safeArrayAccess(String[] arr, int index) {
        try {
            return arr[index].toUpperCase();
        } catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
            // JDK7+ 多异常捕获
            System.out.println("    " + e.getClass().getSimpleName() + ": " + e.getMessage());
            return "(默认值)";
        }
    }

    // 安全除法(演示 catch 顺序:子类在前,父类在后)
    static double safeDivide(String numStr, String denStr) {
        try {
            double num = Double.parseDouble(numStr);
            double den = Double.parseDouble(denStr);
            if (den == 0) throw new ArithmeticException("除数为零");
            return num / den;
        } catch (ArithmeticException e) {
            System.out.println("    算术错误:" + e.getMessage());
            return 0;
        } catch (NumberFormatException e) {
            System.out.println("    格式错误:" + e.getMessage());
            return 0;
        } catch (Exception e) {
            // 兜底:必须放在最后
            System.out.println("    未知错误:" + e.getMessage());
            return 0;
        }
    }

    public static void main(String[] args) {
        System.out.println("===== 输入安全解析器 =====\n");

        // 1. 多级catch:整数解析
        System.out.println("--- 安全解析整数 ---");
        String[] inputs = {"42", "3.14", "abc", null, "  100  "};
        for (String input : inputs) {
            int result = safeParseInt(input);
            System.out.println("  解析 \"" + input + "\" → " + result);
        }

        // 2. JDK7+ 多异常捕获
        System.out.println("\n--- 安全数组访问(多异常捕获)---");
        String[] names = {"Java", null, "Python"};
        System.out.println("  [0] = " + safeArrayAccess(names, 0));
        System.out.println("  [1] = " + safeArrayAccess(names, 1));  // null元素
        System.out.println("  [5] = " + safeArrayAccess(names, 5));  // 越界

        // 3. catch 顺序:子类 → 父类
        System.out.println("\n--- 安全除法(catch顺序)---");
        System.out.printf("  10 / 3 = %.2f%n", safeDivide("10", "3"));
        System.out.printf("  10 / 0 = %.2f%n", safeDivide("10", "0"));
        System.out.printf("  abc / 2 = %.2f%n", safeDivide("abc", "2"));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

运行结果:

===== 输入安全解析器 =====

--- 安全解析整数 ---
  解析 "42" → 42
    格式错误:"3.14" 不是有效整数
  解析 "3.14" → 0
    格式错误:"abc" 不是有效整数
  解析 "abc" → 0
    空值错误:输入为null
  解析 "null" → 0
  解析 "  100  " → 100

--- 安全数组访问(多异常捕获)---
  [0] = JAVA
    NullPointerException: null
  [1] = (默认值)
    ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
  [5] = (默认值)

--- 安全除法(catch顺序)---
  10 / 3 = 3.33
    算术错误:除数为零
  10 / 0 = 0.00
    格式错误:For input string: "abc"
  abc / 2 = 0.00
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 10.2.4 多级catch训练题

训练1:编写一个方法,接收一个字符串数组,尝试将每个元素转换为整数并求和。使用多级 catch 分别处理 NumberFormatException、NullPointerException 和 Exception,打印每次异常的详细信息。

训练2:以下代码能编译通过吗?为什么?

try {
    // ...
} catch (Exception e) {
    System.out.println("父类");
} catch (ArithmeticException e) {
    System.out.println("子类");
}
1
2
3
4
5
6
7

思考:JDK 7 的多异常捕获 catch (A | B e) 中,变量 e 是什么类型?能对 e 重新赋值吗?

# 10.3 finally语句

# 10.3.1 finally基本用法

finally 块中的代码无论是否发生异常都会执行,通常用于释放资源:

FileReader reader = null;
try {
    reader = new FileReader("test.txt");
    // 读取文件
} catch (FileNotFoundException e) {
    System.out.println("文件不存在");
} finally {
    // 释放资源
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 10.3.2 try-with-resources(JDK7+)

JDK 7 引入了 try-with-resources,自动关闭资源:

// 自动关闭 reader,不需要 finally
try (FileReader reader = new FileReader("test.txt");
     BufferedReader br = new BufferedReader(reader)) {
    String line = br.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("IO异常:" + e.getMessage());
}
// reader 和 br 会自动关闭
1
2
3
4
5
6
7
8
9

资源类必须实现 AutoCloseable 接口。

# 10.3.3 综合案例:资源管理对比实验

本案例对比 finally 手动关闭和 try-with-resources 自动关闭,演示资源管理的最佳实践。

/**
 * 资源管理对比实验 —— finally综合案例
 * 知识点:finally执行时机、try-with-resources、AutoCloseable、Suppressed Exception
 */
class MyResource implements AutoCloseable {
    private String name;
    private boolean throwOnClose;

    public MyResource(String name, boolean throwOnClose) {
        this.name = name;
        System.out.println("    [" + name + "] 资源打开");
    }

    public void use() {
        System.out.println("    [" + name + "] 使用资源");
    }

    public void useWithError() {
        System.out.println("    [" + name + "] 使用资源(将抛出异常)");
        throw new RuntimeException(name + " 使用时发生错误");
    }

    @Override
    public void close() {
        System.out.println("    [" + name + "] 资源关闭");
        if (throwOnClose) {
            throw new RuntimeException(name + " 关闭时发生错误");
        }
    }
}

public class ResourceManagementDemo {
    public static void main(String[] args) {
        System.out.println("===== 资源管理对比实验 =====\n");

        // 实验1:finally 手动关闭
        System.out.println("--- 实验1: finally 手动关闭 ---");
        MyResource res = null;
        try {
            res = new MyResource("手动资源", false);
            res.use();
        } catch (Exception e) {
            System.out.println("    捕获: " + e.getMessage());
        } finally {
            if (res != null) res.close();  // 手动关闭
            System.out.println("    finally 执行完毕");
        }

        // 实验2:try-with-resources 自动关闭
        System.out.println("\n--- 实验2: try-with-resources 自动关闭 ---");
        try (MyResource r1 = new MyResource("自动资源A", false);
             MyResource r2 = new MyResource("自动资源B", false)) {
            r1.use();
            r2.use();
        }
        System.out.println("    注意:关闭顺序与打开顺序相反(B先关,A后关)");

        // 实验3:finally 中 return 的陷阱
        System.out.println("\n--- 实验3: finally 中 return ---");
        System.out.println("    结果: " + testFinallyReturn());

        // 实验4:Suppressed Exception
        System.out.println("\n--- 实验4: Suppressed Exception ---");
        try (MyResource r = new MyResource("易碎资源", true)) {
            r.useWithError();  // try 中抛异常
            // close() 也会抛异常 → 成为 suppressed exception
        } catch (Exception e) {
            System.out.println("    主异常: " + e.getMessage());
            for (Throwable s : e.getSuppressed()) {
                System.out.println("    被抑制: " + s.getMessage());
            }
        }
    }

    static int testFinallyReturn() {
        try {
            System.out.println("    try 块执行");
            return 1;
        } finally {
            System.out.println("    finally 块执行");
            // return 2;  // 警告:finally 中的 return 会覆盖 try 中的 return!
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

运行结果:

===== 资源管理对比实验 =====

--- 实验1: finally 手动关闭 ---
    [手动资源] 资源打开
    [手动资源] 使用资源
    [手动资源] 资源关闭
    finally 执行完毕

--- 实验2: try-with-resources 自动关闭 ---
    [自动资源A] 资源打开
    [自动资源B] 资源打开
    [自动资源A] 使用资源
    [自动资源B] 使用资源
    [自动资源B] 资源关闭
    [自动资源A] 资源关闭
    注意:关闭顺序与打开顺序相反(B先关,A后关)

--- 实验3: finally 中 return ---
    try 块执行
    finally 块执行
    结果: 1

--- 实验4: Suppressed Exception ---
    [易碎资源] 资源打开
    [易碎资源] 使用资源(将抛出异常)
    [易碎资源] 资源关闭
    主异常: 易碎资源 使用时发生错误
    被抑制: 易碎资源 关闭时发生错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 10.3.4 finally和try-with-resources训练题

训练1:以下代码的输出是什么?请解释执行顺序:

public static int test() {
    try {
        System.out.println("try");
        return 1;
    } catch (Exception e) {
        System.out.println("catch");
        return 2;
    } finally {
        System.out.println("finally");
    }
}
1
2
3
4
5
6
7
8
9
10
11

训练2:编写一个实现 AutoCloseable 接口的 MyResource 类,在构造方法中打印"资源打开",在 close() 中打印"资源关闭"。用 try-with-resources 创建并使用它,观察输出顺序。

思考:try-with-resources 中如果 try 块抛出异常,close() 也抛出异常,最终抛出的是哪个?另一个去哪了?(提示:Suppressed Exception)

# 10.4 throw和throws

# 10.4.1 throw抛出异常

public void deposit(double amount) {
    if (amount <= 0) {
        throw new IllegalArgumentException("存款金额必须大于0");
    }
    balance += amount;
}
1
2
3
4
5
6

# 10.4.2 throws声明异常

throws 用于在方法签名中声明该方法可能抛出的异常:

// 声明可能抛出 IOException
public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path);
    // ...
}

// 调用方必须处理异常
public void process() {
    try {
        readFile("test.txt");
    } catch (IOException e) {
        System.out.println("文件读取失败");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

对比 C++:C++ 的异常规范(throw())已被弃用,C++11 引入了 noexcept。Java 的 throws 是强制性的(对于检查异常),这使得异常处理更加规范。

# 10.4.3 综合案例:参数校验工具

本案例综合运用 throw 抛出异常和 throws 声明异常,展示异常在方法调用链中的传播。

/**
 * 参数校验工具 —— throw/throws 综合案例
 * 知识点:throw抛出、throws声明、检查异常vs运行时异常、异常传播
 */
public class ParamValidator {

    // throw 运行时异常:调用方可以不处理
    static void validateNotNull(Object obj, String name) {
        if (obj == null) {
            throw new IllegalArgumentException(name + " 不能为null");
        }
    }

    // throw 运行时异常:范围校验
    static void validateRange(int value, int min, int max, String name) {
        if (value < min || value > max) {
            throw new IllegalArgumentException(
                    String.format("%s 超出范围[%d, %d],实际值: %d", name, min, max, value));
        }
    }

    // throws 检查异常:调用方必须处理
    static String validateEmail(String email) throws Exception {
        validateNotNull(email, "email");
        if (!email.contains("@") || !email.contains(".")) {
            throw new Exception("邮箱格式无效: " + email);
        }
        return email.toLowerCase().trim();
    }

    // 异常链传播:底层 → 中层 → 顶层
    static void registerUser(String name, int age, String email)
            throws Exception {
        System.out.println("  注册用户: " + name);
        validateNotNull(name, "用户名");             // 可能抛运行时异常
        validateRange(age, 0, 150, "年龄");          // 可能抛运行时异常
        String validEmail = validateEmail(email);     // 可能抛检查异常
        System.out.println("  ✓ 注册成功: " + name + ", " + age + "岁, " + validEmail);
    }

    public static void main(String[] args) {
        System.out.println("===== 参数校验工具 =====\n");

        // 测试1:正常注册
        System.out.println("--- 测试1: 正常注册 ---");
        try {
            registerUser("张三", 25, "zhangsan@mail.com");
        } catch (Exception e) {
            System.out.println("  ✗ " + e.getMessage());
        }

        // 测试2:null 参数(运行时异常)
        System.out.println("\n--- 测试2: null参数 ---");
        try {
            registerUser(null, 25, "test@mail.com");
        } catch (IllegalArgumentException e) {
            System.out.println("  ✗ 运行时异常: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("  ✗ 检查异常: " + e.getMessage());
        }

        // 测试3:年龄超出范围(运行时异常)
        System.out.println("\n--- 测试3: 年龄超出范围 ---");
        try {
            registerUser("李四", 200, "lisi@mail.com");
        } catch (IllegalArgumentException e) {
            System.out.println("  ✗ 运行时异常: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("  ✗ 检查异常: " + e.getMessage());
        }

        // 测试4:邮箱无效(检查异常)
        System.out.println("\n--- 测试4: 邮箱无效 ---");
        try {
            registerUser("王五", 30, "invalid-email");
        } catch (IllegalArgumentException e) {
            System.out.println("  ✗ 运行时异常: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("  ✗ 检查异常: " + e.getMessage());
        }

        // 对比:throw vs throws
        System.out.println("\n--- throw vs throws 对比 ---");
        System.out.println("  throw  : 在方法体内抛出异常对象");
        System.out.println("  throws : 在方法签名上声明可能的异常类型");
        System.out.println("  运行时异常: 不需要 throws 声明");
        System.out.println("  检查异常  : 必须 throws 声明或 try-catch");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

运行结果:

===== 参数校验工具 =====

--- 测试1: 正常注册 ---
  注册用户: 张三
  ✓ 注册成功: 张三, 25岁, zhangsan@mail.com

--- 测试2: null参数 ---
  注册用户: null
  ✗ 运行时异常: 用户名 不能为null

--- 测试3: 年龄超出范围 ---
  注册用户: 李四
  ✗ 运行时异常: 年龄 超出范围[0, 150],实际值: 200

--- 测试4: 邮箱无效 ---
  注册用户: 王五
  ✗ 检查异常: 邮箱格式无效: invalid-email

--- throw vs throws 对比 ---
  throw  : 在方法体内抛出异常对象
  throws : 在方法签名上声明可能的异常类型
  运行时异常: 不需要 throws 声明
  检查异常  : 必须 throws 声明或 try-catch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 10.4.4 throw和throws训练题

训练1:编写一个方法 int parseInt(String s),如果 s 为 null 抛出 IllegalArgumentException,如果 s 不是合法整数抛出 NumberFormatException。在 main 方法中测试不同的输入。

训练2:以下两种写法有什么区别?什么时候选择哪种?

// 写法1:try-catch 处理
public void method1() {
    try { riskyOperation(); } 
    catch (IOException e) { e.printStackTrace(); }
}

// 写法2:throws 声明
public void method2() throws IOException {
    riskyOperation();
}
1
2
3
4
5
6
7
8
9
10

思考:如果一个方法 throws 了检查异常,所有调用它的方法都必须处理(try-catch 或继续 throws)。这种"异常传播"在大型项目中会导致什么问题?Spring 为什么将大部分检查异常包装成运行时异常?

# 10.5 自定义异常

# 10.5.1 自定义异常类

// 继承 Exception:检查异常(调用方必须处理)
public class InsufficientBalanceException extends Exception {
    private double balance;
    private double amount;

    public InsufficientBalanceException(double balance, double amount) {
        super("余额不足:当前余额 " + balance + ",尝试取款 " + amount);
        this.balance = balance;
        this.amount = amount;
    }

    public double getBalance() { return balance; }
    public double getAmount() { return amount; }
}

// 继承 RuntimeException:运行时异常(调用方可以不处理)
public class InvalidAccountException extends RuntimeException {
    public InvalidAccountException(String message) {
        super(message);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 10.5.2 自定义异常实践

public class Account {
    private String name;
    private double balance;

    public void withdraw(double amount) throws InsufficientBalanceException {
        if (amount > balance) {
            throw new InsufficientBalanceException(balance, amount);
        }
        balance -= amount;
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        Account acc = new Account("张三", 1000);
        try {
            acc.withdraw(2000);
        } catch (InsufficientBalanceException e) {
            System.out.println(e.getMessage());
            System.out.println("当前余额:" + e.getBalance());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 10.5.3 综合案例:订单异常体系

本案例综合运用自定义异常类设计,展示检查异常和运行时异常的选择。

/**
 * 订单异常体系 —— 自定义异常综合案例
 * 知识点:自定义异常继承关系、携带业务信息、异常链
 */

// 自定义异常基类(检查异常)
class OrderException extends Exception {
    private final String orderId;

    public OrderException(String orderId, String message) {
        super(message);
        this.orderId = orderId;
    }

    public String getOrderId() { return orderId; }
}

// 库存不足异常
class StockException extends OrderException {
    private final String product;
    private final int requested;
    private final int available;

    public StockException(String orderId, String product, int requested, int available) {
        super(orderId, String.format("库存不足[%s]:需要%d,仅剩%d", product, requested, available));
        this.product = product;
        this.requested = requested;
        this.available = available;
    }

    public int getShortage() { return requested - available; }
}

// 支付失败异常
class PaymentException extends OrderException {
    public PaymentException(String orderId, String reason) {
        super(orderId, "支付失败: " + reason);
    }
}

// 非法订单异常(运行时异常:程序bug)
class IllegalOrderException extends RuntimeException {
    public IllegalOrderException(String message) {
        super(message);
    }
}

// 简单订单系统
class OrderService {
    static void createOrder(String orderId, String product, int qty, double payment)
            throws OrderException {
        // 参数校验(运行时异常)
        if (orderId == null || orderId.isEmpty()) {
            throw new IllegalOrderException("订单ID不能为空");
        }

        // 库存检查(检查异常)
        int stock = 10;  // 模拟库存
        if (qty > stock) {
            throw new StockException(orderId, product, qty, stock);
        }

        // 支付验证(检查异常)
        double price = qty * 99.9;
        if (payment < price) {
            throw new PaymentException(orderId,
                    String.format("金额不足,需要%.2f,支付%.2f", price, payment));
        }

        System.out.printf("  ✓ 订单[%s] %s×%d 创建成功,支付%.2f%n",
                orderId, product, qty, payment);
    }
}

public class OrderExceptionDemo {
    public static void main(String[] args) {
        System.out.println("===== 订单异常体系演示 =====\n");

        String[][] tests = {
                {"ORD001", "Java教程", "3", "300"},     // 正常
                {"ORD002", "Java教程", "20", "2000"},    // 库存不足
                {"ORD003", "Java教程", "5", "100"},      // 支付不足
                {"", "Java教程", "1", "100"},              // 非法订单ID
        };

        for (String[] t : tests) {
            System.out.println("--- 订单: " + t[0] + " ---");
            try {
                OrderService.createOrder(t[0], t[1],
                        Integer.parseInt(t[2]), Double.parseDouble(t[3]));
            } catch (StockException e) {
                System.out.println("  ✗ [库存异常] " + e.getMessage());
                System.out.println("    订单号: " + e.getOrderId() + ",还差: " + e.getShortage());
            } catch (PaymentException e) {
                System.out.println("  ✗ [支付异常] " + e.getMessage());
                System.out.println("    订单号: " + e.getOrderId());
            } catch (OrderException e) {
                System.out.println("  ✗ [订单异常] " + e.getMessage());
            } catch (IllegalOrderException e) {
                System.out.println("  ✗ [程序错误] " + e.getMessage());
            }
        }

        // 异常继承关系验证
        System.out.println("\n--- 异常继承关系 ---");
        StockException se = new StockException("X", "Y", 1, 0);
        System.out.println("  StockException instanceof OrderException : " + (se instanceof OrderException));
        System.out.println("  StockException instanceof Exception      : " + (se instanceof Exception));
        System.out.println("  选择建议:业务异常→检查异常,程序bug→运行时异常");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

运行结果:

===== 订单异常体系演示 =====

--- 订单: ORD001 ---
  ✓ 订单[ORD001] Java教程×3 创建成功,支付300.00

--- 订单: ORD002 ---
  ✗ [库存异常] 库存不足[Java教程]:需要20,仅剩10
    订单号: ORD002,还差: 10

--- 订单: ORD003 ---
  ✗ [支付异常] 支付失败: 金额不足,需要499.50,支付100.00
    订单号: ORD003

--- 订单:  ---
  ✗ [程序错误] 订单ID不能为空

--- 异常继承关系 ---
  StockException instanceof OrderException : true
  StockException instanceof Exception      : true
  选择建议:业务异常→检查异常,程序bug→运行时异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 10.5.4 自定义异常训练题

训练1:设计一套电商系统的异常体系:

  • OrderException(订单异常基类,检查异常)
  • OrderNotFoundException extends OrderException
  • PaymentFailedException extends OrderException
  • StockInsufficientException extends OrderException

为每个异常类添加有意义的属性(如订单号、商品名、库存数),编写代码模拟下单流程并捕获不同的异常。

训练2:自定义异常应该继承 Exception 还是 RuntimeException?请根据以下场景给出选择并说明理由:

  • (a) 用户密码错误
  • (b) 数据库连接失败
  • (c) 程序内部逻辑 bug(如传入 null 参数)
  • (d) 外部 API 返回错误

疑惑:为什么 Spring 框架把几乎所有异常都定义为 RuntimeException?

答疑:检查异常(Checked Exception)要求每个调用方都必须处理,在大型项目中会导致"异常签名污染"——低层的 SQLException 一路 throws 到最上层,破坏了分层架构的封装性。Spring 的理念是:如果调用方无法有效处理异常(如数据库连接失败),强制要求处理只会产生大量 catch (Exception e) { e.printStackTrace(); } 的无效代码。

论证:

// 检查异常的问题:异常签名层层传播
// DAO → Service → Controller,每一层都要 throws
public User findUser(int id) throws SQLException { ... }           // DAO
public User getUser(int id) throws SQLException { ... }            // Service
public Response handleRequest(int id) throws SQLException { ... }  // Controller

// Spring 的做法:包装为运行时异常
public User findUser(int id) {
    try {
        return jdbcTemplate.queryForObject(...);
    } catch (DataAccessException e) {  // RuntimeException
        // Spring 把 SQLException 包装为 DataAccessException
        throw e;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

结果展示:Kotlin、C# 都没有检查异常,Go 使用错误值而非异常。Java 的检查异常是一个有争议的设计决策。

# 10.6 常见异常汇总

异常 含义 常见场景
NullPointerException 空指针异常 对 null 对象调用方法
ArrayIndexOutOfBoundsException 数组越界 访问超出数组范围的索引
ArithmeticException 算术异常 整数除以0
ClassCastException 类型转换异常 强制转换类型不匹配
NumberFormatException 数字格式异常 字符串转数字失败
IllegalArgumentException 非法参数 方法参数不合法
IOException IO异常 文件读写失败
FileNotFoundException 文件未找到 指定文件不存在
StackOverflowError 栈溢出 递归太深
OutOfMemoryError 内存溢出 堆内存不够

# 10.6.1 综合案例:异常速查手册

本案例汇总演示各种常见异常的触发和处理方式。

/**
 * 异常速查手册 —— 常见异常综合案例
 * 一次性演示 8 种常见异常的触发和处理
 */
public class ExceptionQuickRef {

    static void testException(String name, Runnable action) {
        System.out.printf("%-35s", "  " + name + ": ");
        try {
            action.run();
            System.out.println("✓ 正常");
        } catch (Exception e) {
            System.out.println("✗ " + e.getClass().getSimpleName()
                    + " → " + e.getMessage());
        } catch (Error e) {
            System.out.println("✗ " + e.getClass().getSimpleName()
                    + " → " + e.getMessage());
        }
    }

    static int recursion(int n) { return recursion(n + 1); }

    public static void main(String[] args) {
        System.out.println("===== 常见异常速查手册 =====\n");

        // 1. NullPointerException
        testException("NullPointerException",
                () -> { String s = null; s.length(); });

        // 2. ArrayIndexOutOfBoundsException
        testException("ArrayIndexOutOfBoundsException",
                () -> { int[] a = {1}; int x = a[5]; });

        // 3. ArithmeticException
        testException("ArithmeticException",
                () -> { int x = 10 / 0; });

        // 4. ClassCastException
        testException("ClassCastException",
                () -> { Object o = "abc"; Integer i = (Integer) o; });

        // 5. NumberFormatException
        testException("NumberFormatException",
                () -> Integer.parseInt("abc"));

        // 6. IllegalArgumentException
        testException("IllegalArgumentException",
                () -> { throw new IllegalArgumentException("参数不合法"); });

        // 7. StringIndexOutOfBoundsException
        testException("StringIndexOutOfBoundsException",
                () -> "abc".charAt(10));

        // 8. StackOverflowError
        testException("StackOverflowError",
                () -> recursion(0));

        System.out.println("\n--- 异常分类总结 ---");
        System.out.println("  运行时异常(RuntimeException): 编译器不强制处理");
        System.out.println("    → NPE, 数组越界, 算术异常, 类型转换, 数字格式");
        System.out.println("  检查异常(Checked Exception): 编译器强制处理");
        System.out.println("    → IOException, SQLException, FileNotFoundException");
        System.out.println("  错误(Error): 严重问题,一般不捕获");
        System.out.println("    → StackOverflowError, OutOfMemoryError");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

运行结果:

===== 常见异常速查手册 =====

  NullPointerException             : ✗ NullPointerException → null
  ArrayIndexOutOfBoundsException   : ✗ ArrayIndexOutOfBoundsException → Index 5 out of bounds for length 1
  ArithmeticException              : ✗ ArithmeticException → / by zero
  ClassCastException               : ✗ ClassCastException → class java.lang.String cannot be cast to class java.lang.Integer
  NumberFormatException            : ✗ NumberFormatException → For input string: "abc"
  IllegalArgumentException         : ✗ IllegalArgumentException → 参数不合法
  StringIndexOutOfBoundsException  : ✗ StringIndexOutOfBoundsException → String index out of range: 10
  StackOverflowError               : ✗ StackOverflowError → null

--- 异常分类总结 ---
  运行时异常(RuntimeException): 编译器不强制处理
    → NPE, 数组越界, 算术异常, 类型转换, 数字格式
  检查异常(Checked Exception): 编译器强制处理
    → IOException, SQLException, FileNotFoundException
  错误(Error): 严重问题,一般不捕获
    → StackOverflowError, OutOfMemoryError
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 10.6.2 常见异常训练题

训练1:编写代码分别触发以下异常,并用 try-catch 捕获并打印友好信息:NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException、NumberFormatException。

训练2:Error 和 Exception 有什么区别?StackOverflowError 可以被 try-catch 捕获吗?如果可以,捕获后程序能正常运行吗?

思考:为什么 NullPointerException 是运行时异常而不是检查异常?如果它是检查异常,Java 代码会变成什么样?

上次更新: 2026/06/10, 11:13:41
接口和抽象类
集合框架

← 接口和抽象类 集合框架→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式