从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

    • 我的 Java 学习路线
    • 安装 Java
    • Java数据类型

    • Java 多版本配置
    • 面向对象

    • Java核心类

    • IO

    • Java与时间

    • 异常处理

      • 什么是异常
      • 捕获和抛出异常
      • 深入异常
        • 从异常中获取信息
        • finally
        • 异常屏蔽
        • 使用异常的优点和缺点
        • 重新抛出异常
        • 链式异常
        • 从异常中获取信息
        • ‍ ex.getMessage()的输出为5,这个就是越界的下标 ‍
        • 就是3个字符串:异常类的全名 + ":" + getMessage(),也不用过多解释 ‍
        • finally
        • 异常屏蔽
        • 使用异常的优点和缺点
        • 重新抛出异常
        • 链式异常
      • 自定义异常
      • 空指针异常
      • return 和 finally 谁先执行
    • 哈希和加密算法

    • Java8新特性

    • 网络编程

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • JavaSE
  • 异常处理
2023-02-13
目录

深入异常

# 深入异常

本文我们来讲解更多关于异常的信息

# 从异常中获取信息

异常对象包含关于异常的有价值的信息。可以利用下面这些 java.lang.Throwable 类中的实例方法获取有关异常的信息:

  • +getMessage():String 返回描述该异常对象的信息
  • +toString():String 返回三个字符串的连接:异常类的全名,":"冒号和空白,getMessage() 的返回
  • +printStackTrace():void 在控制台上打印 Throwable 对象和它的调用堆栈信息。
  • +getStackTrace():StackTraceElemnet[] 返回和该异常对象相关的代表堆栈跟踪的一个堆栈跟踪元素的数组

我们来演示下上述方法,首先来看这段代码:

public class LearnExceptionDemo3 {
  public static void main(String[] args) {
    try {
      int[] list = {1,2,3,4,5};
      System.out.println(sum(list));
    } catch (Exception ex) {
      ex.printStackTrace();

      System.out.println("ex.getMessage(): " + ex.getMessage());
      System.out.println("ex.toString(): " + ex.toString());

      StackTraceElement[] traceElements = ex.getStackTrace();
      for (int i = 0; i < traceElements.length; i++) {
        System.out.print("method: " + traceElements[i].getMethodName() + ". ");
        System.out.print("class: " + traceElements[i].getClassName() + ". ");
        System.out.println("LineNumber: " + traceElements[i].getLineNumber() +". ");
      }
    }
  }

  static int sum(int[] list){
    int result = 0;
    for (int i = 0; i <= list.length; i++) {
      result += list[i];
    }
    return result;
  }
}

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

在 sum 方法里,求和的时候,由于数组越界,会抛出一个异常,然后被 main 方法的 try-catch 块处理。

运行结果:

$ javac LearnExceptionDemo3.java
$ java LearnExceptionDemo3  
java.lang.ArrayIndexOutOfBoundsException: 5
        at LearnExceptionDemo3.sum(LearnExceptionDemo3.java:24)
        at LearnExceptionDemo3.main(LearnExceptionDemo3.java:5)
ex.getMessage(): 5
ex.toString(): java.lang.ArrayIndexOutOfBoundsException: 5
method: sum. class: LearnExceptionDemo3. LineNumber: 24.
method: main. class: LearnExceptionDemo3. LineNumber: 5.
1
2
3
4
5
6
7
8
9

我们依次来分析下。首先 ex.printStackTrace() 的输出如下:

java.lang.ArrayIndexOutOfBoundsException: 5
        at LearnExceptionDemo3.sum(LearnExceptionDemo3.java:24)
        at LearnExceptionDemo3.main(LearnExceptionDemo3.java:5)
1
2
3

第一行说明了异常的类型,从名字可以看出是数组越界了,并且贴心的告诉你越界的下标是 5. 注意,不同异常的输出也不同,例如如果输出为 0,则打印的字符串是这样子的:java.lang.ArithmeticException: / by zero,也就是会告诉具体是为什么抛出这个异常(除以 0 了)。

第二行就是具体哪个方法抛出的。

第三行就是哪个方法调用了抛出异常的代码(注意,可能不止 3 行,这得看方法调用链有多少个)

从下往上看,可以看到调用的层次,这里是 main 调用了 sum 方法


ex.getMessage() 的输出为 5,这个就是越界的下标


ex.toString() 的输出:

java.lang.ArrayIndexOutOfBoundsException: 5
1

就是 3 个字符串:异常类的全名 + ":" +getMessage(),也不用过多解释


接下来我们来看看 ex.getStackTrace();,其返回一个 StackTraceElement 的数组。那么什么是 StackTraceElement 呢?

StackTrace(堆栈轨迹)存放的就是方法调用栈的信息,每次调用一个方法会产生一个方法栈,当前方法调用另外一个方法时会使用栈将当前方法的现场信息保存在此方法栈当中,获取这个栈就可以得到方法调用的详细过程。

然后我们打印其中一个 StackTraceElement 的信息,可以得到调用方法名、类名和发生异常的行号,输出如下:

method: sum. class: LearnExceptionDemo3. LineNumber: 24.
method: main. class: LearnExceptionDemo3. LineNumber: 5.
1
2

# finally

有时候,不论异常是否出现或者是否被捕获,都希望执行某些代码,例如关闭数据库连接,或者关闭 IO 流。Java 有一个 finally 子句,可以用来达到这个目的,格式如下:

try {
	statements; 
}
catch(TheException ex){
	handling ex; 
}
finally {
	finalStatements; 
}
1
2
3
4
5
6
7
8
9

使用 finally 子句时,可以省略掉 catch 块。

在任何情况下,finally 块中的代码都会执行,不论 try 块中是否出现异常或者是否被捕获:

  • 如果 try 块中没有出现异常,执行 finalStatements, 然后执行 try 块的下一条语句。
  • 如果 try 块中有一条语句引起异常,并被 catch 块捕获,则执行 catch 块和 finally 子句。然后执行 try 块之后的下一条语句。
  • 如果 try 块中有一条语句引起异常,但是没有被任何 catch 块捕获,就会跳过 try 块中的其他语句,执行 finally 语句,并且将异常传递给这个方法的调用者。
  • 即使在到达 finally 块之前有一个 return 语句,finally 块还是会执行。

# 异常屏蔽

如果在执行 finally 语句时抛出异常,那么,catch 语句的异常还能否继续抛出?例如:

public class LearnExceptionDemo4 {
  public static void main(String[] args) {
    try {
      Integer.parseInt("abc");
    } catch (Exception e) {
      System.out.println("catched");
      throw new RuntimeException(e);
    }finally {
      System.out.println("finally");
      throw new IllegalArgumentException();
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

执行上述代码,发现异常信息如下:

catched
finally
Exception in thread "main" java.lang.IllegalArgumentException
        at LearnExceptionDemo4.main(LearnExceptionDemo4.java:10)
1
2
3
4

这说明 finally抛出异常后,原来在 catch中准备抛出的 RuntimeException 异常就“消失”了,因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常(Suppressed Exception)。

在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用一个 origin 变量保存原始异常,然后调用 Throwable.addSuppressed(),把原始异常添加进来,最后在 finally 抛出:

public class LearnExceptionDemo5 {
  public static void main(String[] args) throws Exception{
    Exception origin = null;
    try {
      Integer.parseInt("abc");
    } catch (Exception e) {
      origin = e;
      throw e;
    }finally {
      Exception e = new IllegalArgumentException();
      if(null != origin){
        e.addSuppressed(origin);
      }
      throw e;
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

运行结果:

Exception in thread "main" java.lang.IllegalArgumentException
        at LearnExceptionDemo5.main(LearnExceptionDemo5.java:10)
        Suppressed: java.lang.NumberFormatException: For input string: "abc"
                at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)   
                at java.lang.Integer.parseInt(Integer.java:580)
                at java.lang.Integer.parseInt(Integer.java:615)
                at LearnExceptionDemo5.main(LearnExceptionDemo5.java:5)
1
2
3
4
5
6
7

当 catch 和 finally 都抛出了异常时,虽然 catch 的异常被屏蔽了,但是,finally 抛出的异常仍然包含了它。

通过 Throwable.getSuppressed() 可以获取所有的 Suppressed Exception。

绝大多数情况下,在 finally 中不要抛出异常。因此,我们通常不需要关心 Suppressed Exception。

# 使用异常的优点和缺点

优点:try 块包含正常情况下执行的代码。catch 块包含异常情况下执行的代码。异常处理将错误处理代码从正常的程序设计任务中分离出来,这样,可以使程序更易读、更易修改。

缺点:由于异常处理需要初始化新的异常对象,需要从调用栈返回,而且还需要沿着方法调用链来传播异常以便找到它的异常处理器,所以,异常处理通常需要更多的时间和资源。

那么什么时候应该使用异常呢?如果想让该方法的调用者处理异常,应该创建一个异常对象并将其抛出。如果能在发生异常的方法中处理异常,那么就不需要抛出或使用异常。 — 般来说,一个项目中多个类都会发生的共同异常应该考虑作为一种异常类。对于发生在个别方法中的简单错误最好进行局部处理,无须抛出异常。

一句话:当错误需要被方法的调用者处理的时候,方法应该抛出一个异常。

# 重新抛出异常

如果异常处理器不能处理一个异常,或者只是简单地希望它的调用者注意到该异常,Java 允许该异常处理器重新抛出异常,格式如下:

try {
    statements; 
}
catch(TheException ex){
    perform operations before exits;
    throw ex; 
}
1
2
3
4
5
6
7

语句 throw ex 重新抛出异常给调用者,以便调用者的其他处理器获得处理异常 ex 的机会。

# 链式异常

链式异常是我们工作中经常会遇到的,请读者好好掌握。

在上一小节,我们让 catch 块重新抛出原始的异常。

但有时候,可能需要同原始异常一起抛出一个新异常(带有附加信息),这称为链式异常( chained exception )。我们来看下面的代码:

public class ChainedExceptionDemo {
  public static void main(String[] args) {
    try {
      method1();
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  public static void method1() throws Exception {
    try {
      method2();
    }
    catch (Exception ex) {
      throw new Exception("New info from method1", ex);
    }
  }

  public static void method2() throws Exception {
    throw new Exception("New info from method2");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

我们来分析下运行结果:

  1. main 方法调用 method1
  2. method1 调用 method2
  3. method2 抛出一个异常,该异常被 method1 的 catch 块捕获
  4. method1 将异常包装成一个新的异常,并且抛出
  5. main 方法捕获该异常,并输出

运行结果:

$ javac ChainedExceptionDemo.java
$ java ChainedExceptionDemo
java.lang.Exception: New info from method1
        at ChainedExceptionDemo.method1(ChainedExceptionDemo.java:16)
        at ChainedExceptionDemo.main(ChainedExceptionDemo.java:4)
Caused by: java.lang.Exception: New info from method2
        at ChainedExceptionDemo.method2(ChainedExceptionDemo.java:21)
        at ChainedExceptionDemo.method1(ChainedExceptionDemo.java:13)
        ... 1 more
1
2
3
4
5
6
7
8
9

可以看到,首先输出 method1 抛出的新异常,然后显示 method2 抛出的新异常

注意到 Caused by: Xxx在 method2 的那里,说明 method1 里捕获的 Exception并不是造成问题的根源,根源在于 method2的 Exception,是在 method2()方法抛出的。

throw new Exception("New info from method1", ex);,其实内部调用的就是 initCause 方法:

 catch (Exception ex) {
  Exception ex2 = new Exception("New info from method1");
  ex2.initCause(ex);
  throw ex2;
}
1
2
3
4
5

运行结果:

java.lang.Exception: New info from method1
    at chapter10Exception.ChainedExceptionDemo2.method1(ChainedExceptionDemo2.java:18)
    at chapter10Exception.ChainedExceptionDemo2.main(ChainedExceptionDemo2.java:6)
    Caused by: java.lang.Exception: New info from method2
    at chapter10Exception.ChainedExceptionDemo2.method2(ChainedExceptionDemo2.java:25)
    at chapter10Exception.ChainedExceptionDemo2.method1(ChainedExceptionDemo2.java:15)
	... 1 more
1
2
3
4
5
6
7

因此,我们一般直接使用 throw new Exception("New info from method1", ex); 即可,较少使用 initCause

本文我们来讲解更多关于异常的信息

# 从异常中获取信息

异常对象包含关于异常的有价值的信息。可以利用下面这些 java.lang.Throwable 类中的实例方法获取有关异常的信息:

  • +getMessage():String 返回描述该异常对象的信息
  • +toString():String 返回三个字符串的连接:异常类的全名,":"冒号和空白,getMessage()的返回
  • +printStackTrace():void 在控制台上打印Throwable对象和它的调用堆栈信息。
  • +getStackTrace():StackTraceElemnet[] 返回和该异常对象相关的代表堆栈跟踪的一个堆栈跟踪元素的数组 ‍ 我们来演示下上述方法,首先来看这段代码:
public class LearnExceptionDemo3 {
  public static void main(String[] args) {
    try {
      int[] list = {1,2,3,4,5};
      System.out.println(sum(list));
    } catch (Exception ex) {
      ex.printStackTrace();

      System.out.println("ex.getMessage(): " + ex.getMessage());
      System.out.println("ex.toString(): " + ex.toString());

      StackTraceElement[] traceElements = ex.getStackTrace();
      for (int i = 0; i < traceElements.length; i++) {
        System.out.print("method: " + traceElements[i].getMethodName() + ". ");
        System.out.print("class: " + traceElements[i].getClassName() + ". ");
        System.out.println("LineNumber: " + traceElements[i].getLineNumber() +". ");
      }
    }
  }

  static int sum(int[] list){
    int result = 0;
    for (int i = 0; i <= list.length; i++) {
      result += list[i];
    }
    return result;
  }
}

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

在sum方法里,求和的时候,由于数组越界,会抛出一个异常,然后被main方法的try-catch块处理。 ‍ 运行结果:

$ javac LearnExceptionDemo3.java
$ java LearnExceptionDemo3  
java.lang.ArrayIndexOutOfBoundsException: 5
        at LearnExceptionDemo3.sum(LearnExceptionDemo3.java:24)
        at LearnExceptionDemo3.main(LearnExceptionDemo3.java:5)
ex.getMessage(): 5
ex.toString(): java.lang.ArrayIndexOutOfBoundsException: 5
method: sum. class: LearnExceptionDemo3. LineNumber: 24.
method: main. class: LearnExceptionDemo3. LineNumber: 5.
1
2
3
4
5
6
7
8
9

‍ 我们依次来分析下。首先ex.printStackTrace() 的输出如下:

java.lang.ArrayIndexOutOfBoundsException: 5
        at LearnExceptionDemo3.sum(LearnExceptionDemo3.java:24)
        at LearnExceptionDemo3.main(LearnExceptionDemo3.java:5)
1
2
3

第一行说明了异常的类型,从名字可以看出是数组越界了,并且贴心的告诉你越界的下标是5. 注意,不同异常的输出也不同,例如如果输出为0,则打印的字符串是这样子的:java.lang.ArithmeticException: / by zero,也就是会告诉具体是为什么抛出这个异常(除以0了)。

第二行就是具体哪个方法抛出的。

第三行就是哪个方法调用了抛出异常的代码(注意,可能不止3行,这得看方法调用链有多少个)

从下往上看,可以看到调用的层次,这里是main调用了sum方法


# ‍ ex.getMessage()的输出为5,这个就是越界的下标 ‍

ex.toString() 的输出:

java.lang.ArrayIndexOutOfBoundsException: 5
1

# 就是3个字符串:异常类的全名 + ":" +getMessage(),也不用过多解释 ‍

接下来我们来看看ex.getStackTrace();,其返回一个StackTraceElement的数组。那么什么是StackTraceElement呢?

StackTrace(堆栈轨迹)存放的就是方法调用栈的信息,每次调用一个方法会产生一个方法栈,当前方法调用另外一个方法时会使用栈将当前方法的现场信息保存在此方法栈当中,获取这个栈就可以得到方法调用的详细过程。

然后我们打印其中一个StackTraceElement的信息,可以得到调用方法名、类名和发生异常的行号,输出如下:

method: sum. class: LearnExceptionDemo3. LineNumber: 24.
method: main. class: LearnExceptionDemo3. LineNumber: 5.
1
2

# finally

有时候,不论异常是否出现或者是否被捕获,都希望执行某些代码,例如关闭数据库连接,或者关闭IO流。Java 有一个finally 子句,可以用来达到这个目的,格式如下:

try {
	statements; 
}
catch(TheException ex){
	handling ex; 
}
finally {
	finalStatements; 
}
1
2
3
4
5
6
7
8
9

使用 finally 子句时,可以省略掉 catch 块。 ‍ 在任何情况下,finally 块中的代码都会执行,不论 try 块中是否出现异常或者是否被捕获:

  • 如果 try 块中没有出现异常,执行 finalStatements, 然后执行 try 块的下一条语句。
  • 如果 try 块中有一条语句引起异常,并被 catch 块捕获,则执行 catch 块和 finally 子句。然后执行 try 块之后的下一条语句。
  • 如果 try 块中有一条语句引起异常,但是没有被任何 catch 块捕获,就会跳过 try 块中的其他语句,执行 finally 语句,并且将异常传递给这个方法的调用者。
  • 即使在到达 finally 块之前有一个 return 语句,finally 块还是会执行。 ‍

# 异常屏蔽

如果在执行finally语句时抛出异常,那么,catch语句的异常还能否继续抛出?例如:

public class LearnExceptionDemo4 {
  public static void main(String[] args) {
    try {
      Integer.parseInt("abc");
    } catch (Exception e) {
      System.out.println("catched");
      throw new RuntimeException(e);
    }finally {
      System.out.println("finally");
      throw new IllegalArgumentException();
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

执行上述代码,发现异常信息如下:

catched
finally
Exception in thread "main" java.lang.IllegalArgumentException
        at LearnExceptionDemo4.main(LearnExceptionDemo4.java:10)
1
2
3
4

这说明finally抛出异常后,原来在catch中准备抛出的RuntimeException异常就“消失”了,因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常(Suppressed Exception)。

在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用一个origin变量保存原始异常,然后调用Throwable.addSuppressed(),把原始异常添加进来,最后在finally抛出:

public class LearnExceptionDemo5 {
  public static void main(String[] args) throws Exception{
    Exception origin = null;
    try {
      Integer.parseInt("abc");
    } catch (Exception e) {
      origin = e;
      throw e;
    }finally {
      Exception e = new IllegalArgumentException();
      if(null != origin){
        e.addSuppressed(origin);
      }
      throw e;
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

‍ 运行结果:

Exception in thread "main" java.lang.IllegalArgumentException
        at LearnExceptionDemo5.main(LearnExceptionDemo5.java:10)
        Suppressed: java.lang.NumberFormatException: For input string: "abc"
                at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)   
                at java.lang.Integer.parseInt(Integer.java:580)
                at java.lang.Integer.parseInt(Integer.java:615)
                at LearnExceptionDemo5.main(LearnExceptionDemo5.java:5)
1
2
3
4
5
6
7

当catch和finally都抛出了异常时,虽然catch的异常被屏蔽了,但是,finally抛出的异常仍然包含了它

通过Throwable.getSuppressed()可以获取所有的Suppressed Exception。

绝大多数情况下,在finally中不要抛出异常。因此,我们通常不需要关心Suppressed Exception。

# 使用异常的优点和缺点

优点:try 块包含正常情况下执行的代码。catch 块包含异常情况下执行的代码。异常处理将错误处理代码从正常的程序设计任务中分离出来,这样,可以使程序更易读、更易修改。

缺点:由于异常处理需要初始化新的异常对象,需要从调用栈返回,而且还需要沿着方法调用链来传播异常以便找到它的异常处理器,所以,异常处理通常需要更多的时间和资源。 ‍ 那么什么时候应该使用异常呢?如果想让该方法的调用者处理异常,应该创建一个异常对象并将其抛出。如果能在发生异常的方法中处理异常,那么就不需要抛出或使用异常。 — 般来说,一个项目中多个类都会发生的共同异常应该考虑作为一种异常类。对于发生在个别方法中的简单错误最好进行局部处理,无须抛出异常。

一句话:当错误需要被方法的调用者处理的时候,方法应该抛出一个异常。 ‍

# 重新抛出异常

如果异常处理器不能处理一个异常,或者只是简单地希望它的调用者注意到该异常,Java 允许该异常处理器重新抛出异常,格式如下:

try {
    statements; 
}
catch(TheException ex){
    perform operations before exits;
    throw ex; 
}
1
2
3
4
5
6
7

语句 throw ex 重新抛出异常给调用者,以便调用者的其他处理器获得处理异常 ex 的机会。 ‍

# 链式异常

链式异常是我们工作中经常会遇到的,请读者好好掌握。

在上一小节,我们让 catch 块重新抛出原始的异常。

但有时候,可能需要同原始异常一起抛出一个新异常(带有附加信息),这称为链式异常( chained exception )。我们来看下面的代码:

public class ChainedExceptionDemo {
  public static void main(String[] args) {
    try {
      method1();
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  public static void method1() throws Exception {
    try {
      method2();
    }
    catch (Exception ex) {
      throw new Exception("New info from method1", ex);
    }
  }

  public static void method2() throws Exception {
    throw new Exception("New info from method2");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

我们来分析下运行结果:

  1. main方法调用method1
  2. method1调用method2
  3. method2抛出一个异常,该异常被method1的catch块捕获
  4. method1将异常包装成一个新的异常,并且抛出
  5. main方法捕获该异常,并输出

运行结果:

$ javac ChainedExceptionDemo.java
$ java ChainedExceptionDemo
java.lang.Exception: New info from method1
        at ChainedExceptionDemo.method1(ChainedExceptionDemo.java:16)
        at ChainedExceptionDemo.main(ChainedExceptionDemo.java:4)
Caused by: java.lang.Exception: New info from method2
        at ChainedExceptionDemo.method2(ChainedExceptionDemo.java:21)
        at ChainedExceptionDemo.method1(ChainedExceptionDemo.java:13)
        ... 1 more
1
2
3
4
5
6
7
8
9

可以看到,首先输出method1抛出的新异常,然后显示method2抛出的新异常 ‍ 注意到Caused by: Xxx在method2的那里,说明method1里捕获的Exception并不是造成问题的根源,根源在于method2的Exception,是在method2()方法抛出的。

throw new Exception("New info from method1", ex);,其实内部调用的就是initCause方法:

 catch (Exception ex) {
  Exception ex2 = new Exception("New info from method1");
  ex2.initCause(ex);
  throw ex2;
}
1
2
3
4
5

运行结果:




 




java.lang.Exception: New info from method1
    at chapter10Exception.ChainedExceptionDemo2.method1(ChainedExceptionDemo2.java:18)
    at chapter10Exception.ChainedExceptionDemo2.main(ChainedExceptionDemo2.java:6)
    Caused by: java.lang.Exception: New info from method2
    at chapter10Exception.ChainedExceptionDemo2.method2(ChainedExceptionDemo2.java:25)
    at chapter10Exception.ChainedExceptionDemo2.method1(ChainedExceptionDemo2.java:15)
	... 1 more
1
2
3
4
5
6
7

因此,我们一般直接使用throw new Exception("New info from method1", ex);即可,较少使用initCause

上次更新: 2025/5/5 17:15:09
捕获和抛出异常
自定义异常

← 捕获和抛出异常 自定义异常→

最近更新
01
吐槽一下《僵尸校园》
05-15
02
2025 年 4 月记
04-30
03
山西大同 “订婚强奸案” 将会给整个社会带来的影响有多严重? - 知乎 转载
04-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式