网站首页 > 博客文章 正文
异常的传播
当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个try ... catch被捕获为止:
// exception public class Main { public static void main(String[] args) { try { process1(); } catch (Exception e) { e.printStackTrace(); } } static void process1() { process2(); } static void process2() { Integer.parseInt(null); // 会抛出NumberFormatException } }
通过printStackTrace()可以打印出方法的调用栈,类似:
java.lang.NumberFormatException: null at java.base/java.lang.Integer.parseInt(Integer.java:614) at java.base/java.lang.Integer.parseInt(Integer.java:770) at Main.process2(Main.java:16) at Main.process1(Main.java:12) at Main.main(Main.java:5)
printStackTrace()对于调试错误非常有用,上述信息表示:NumberFormatException是在java.lang.Integer.parseInt方法中被抛出的,调用层次从上到下依次是:
- main()调用process1();
- process1()调用process2();
- process2()调用Integer.parseInt(String);
- Integer.parseInt(String)调用Integer.parseInt(String, int)。
查看Integer.java源码可知,抛出异常的方法代码如下:
public static int parseInt(String s, int radix) throws NumberFormatException { if (s == null) { throw new NumberFormatException("null");//源码内有这个异常抛出 } ... }
并且,每层调用均给出了源代码的行号,可直接定位。
抛出异常
当发生错误时,例如,用户输入了非法的字符,我们就可以抛出异常。
如何抛出异常?参考Integer.parseInt()方法,抛出异常分两步:
- 创建某个Exception的实例;
- 用throw语句抛出。
下面是一个例子:
void process2(String s) { if (s==null) { NullPointerException e = new NullPointerException(); throw e; } }
实际上,绝大部分抛出异常的代码都会合并写成一行:
void process2(String s) { if (s==null) { throw new NullPointerException(); } }
如果一个方法捕获了某个异常后,又在catch子句中抛出新的异常,就相当于把抛出的异常类型“转换”了:
void process1(String s) { try { process2(); } catch (NullPointerException e) { throw new IllegalArgumentException(); } } void process2(String s) { if (s==null) { throw new NullPointerException(); } }
当process2()抛出NullPointerException后,被process1()捕获,然后抛出IllegalArgumentException()。
如果在main()中捕获IllegalArgumentException,我们看看打印的异常栈:
// exception public class Main { public static void main(String[] args) { try { process1(); } catch (Exception e) { e.printStackTrace(); } } static void process1() { try { process2(); } catch (NullPointerException e) { throw new IllegalArgumentException(); } } static void process2() { throw new NullPointerException(); } }
这说明新的异常丢失了原始异常信息,我们已经看不到原始异常NullPointerException的信息了。
为了能追踪到完整的异常栈,在构造异常的时候,把原始的Exception实例传进去,新的Exception就可以持有原始Exception信息。对上述代码改进如下:
// exception public class Main { public static void main(String[] args) { try { process1(); } catch (Exception e) { e.printStackTrace(); } } static void process1() { try { process2(); } catch (NullPointerException e) { throw new IllegalArgumentException(e); ///注意 带着之前的异常一起抛出 } } static void process2() { throw new NullPointerException(); } }
注意到Caused by: Xxx,说明捕获的IllegalArgumentException并不是造成问题的根源,根源在于NullPointerException,是在Main.process2()方法抛出的。
在代码中获取原始异常可以使用Throwable.getCause()方法。如果返回null,说明已经是“根异常”了。
有了完整的异常栈的信息,我们才能快速定位并修复代码的问题。
如果我们在try或者catch语句块中抛出异常,finally语句是否会执行?例如:
// exception public class Main { 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"); } } }
上述代码执行结果如下:
catched finally Exception in thread "main" java.lang.RuntimeException: java.lang.NumberFormatException: For input string: "abc" at Main.main(Main.java:8) Caused by: java.lang.NumberFormatException: For input string: "abc" at ...
第一行打印了catched,说明进入了catch语句块。第二行打印了finally,说明执行了finally语句块。
因此,在catch中抛出异常,不会影响finally的执行。JVM会先执行finally,然后抛出异常。
异常屏蔽
如果在执行finally语句时抛出异常,那么,catch语句的异常还能否继续抛出?例如:
// exception public class Main { 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();//因为先执行finally,这样会屏蔽之前的异常 } } } 执行上述代码,发现异常信息如下: catched finally Exception in thread "main" java.lang.IllegalArgumentException at Main.main(Main.java:11)
这说明finally抛出异常后,原来在catch中准备抛出的异常就“消失”了,因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常(Suppressed Exception)。
在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用origin变量保存原始异常,然后调用Throwable.addSuppressed(),把原始异常添加进来,最后在finally抛出:
// exception public class Main { public static void main(String[] args) throws Exception { Exception origin = null; try { System.out.println(Integer.parseInt("abc")); } catch (Exception e) { origin = e; throw e; } finally { Exception e = new IllegalArgumentException(); if (origin != null) { e.addSuppressed(origin); } throw e; } } }
当catch和finally都抛出了异常时,虽然catch的异常被屏蔽了,但是,finally抛出的异常仍然包含了它:
Exception in thread "main" java.lang.IllegalArgumentException at Main.main(Main.java:11) Suppressed: java.lang.NumberFormatException: For input string: "abc" at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.base/java.lang.Integer.parseInt(Integer.java:652) at java.base/java.lang.Integer.parseInt(Integer.java:770) at Main.main(Main.java:6)
通过Throwable.getSuppressed()可以获取所有的Suppressed Exception。
绝大多数情况下,在finally中不要抛出异常。因此,我们通常不需要关心Suppressed Exception。
小结
调用printStackTrace()可以打印异常的传播栈,对于调试非常有用;
捕获异常并再次抛出新的异常时,应该持有原始异常信息;
通常不要在finally中抛出异常。如果在finally中抛出异常,应该原始异常加入到原有异常中。调用方可通过Throwable.getSuppressed()获取所有添加的Suppressed Exception。
【关键:
- 异常传播:抛出异常,向上传播
- 如何抛出异常? -----创建某个Exception 然后throw
- 为了能追踪到完整的异常栈,在构造异常的时候,把原始的Exception实例传进去,新的Exception就可以持有原始Exception信息 ,在 抛出异常的时候类似:throw new IllegalArgumentException(e);
- 在代码中获取原始异常可以使用Throwable.getCause()方法。如果返回null,说明已经是“根异常”了
- 在catch中抛出异常,不会影响finally的执行。JVM会先执行finally,然后抛出异常
- 而finally中抛出异常,会屏蔽之前的捕获的异常,如何保存所有的异常信息?方法是先用origin变量保存原始异常,然后调用Throwable.addSuppressed(),把原始异常添加进来
通过Throwable.getSuppressed()可以获取所有的Suppressed Exception。
- 绝大多数情况下,在finally中不要抛出异常。因此,我们通常不需要关心Suppressed Exception。
】
猜你喜欢
- 2024-10-10 开发中必须要掌握的 Git 技巧(git开源项目)
- 2024-10-10 分享几个很赞的git学习资源网(git视频教程)
- 2024-10-10 过来人告诉你,去工作前最好还是学学Git
- 2024-10-10 Github标星10.8K!Java 实战博客项目分享
- 2024-10-10 java开发转行大数据开发的学习路径
- 2024-10-10 Java入门二之Lambda 表达式(java的lambada表达式)
- 2024-10-10 大学毕业如何找到一份10K+月薪的JAVA工程师工作
- 2024-10-10 假如从5月开始学Java(java如何从零学起)
- 2024-10-10 学习廖雪峰的JAVA教程---泛型(super通配符 super T>Pair>)
- 2024-10-10 学习廖雪峰的JAVA教程---异常处理(使用Log4j 日志)
你 发表评论:
欢迎- 07-02在线学习在爱奇艺信息流推荐业务中的探索与实践
- 07-02Diallyl Trisulfide(H2S donor)二烯丙基三硫:合成方法与工艺
- 07-02MitoSOX Red Mitochondrial Superoxide Indicator使用方法
- 07-02深度时空网络、记忆网络与特征表达学习在 CTR 预估中的应用
- 07-02iFluor 488标记鬼笔环肽可通过标记F-actin,研究细胞在迁移等
- 07-02快速了解红色线粒体超氧化物荧光探针的基本特性
- 07-02腔肠素400A(Coelenteramine 400a)综合解析,一文掌握所有要点!
- 07-02Chinese doctor Zhang Junqiao's heroic act exemplifies deep China-Africa friendship: FM spokesperson
- 最近发表
-
- 在线学习在爱奇艺信息流推荐业务中的探索与实践
- Diallyl Trisulfide(H2S donor)二烯丙基三硫:合成方法与工艺
- MitoSOX Red Mitochondrial Superoxide Indicator使用方法
- 深度时空网络、记忆网络与特征表达学习在 CTR 预估中的应用
- iFluor 488标记鬼笔环肽可通过标记F-actin,研究细胞在迁移等
- 快速了解红色线粒体超氧化物荧光探针的基本特性
- 腔肠素400A(Coelenteramine 400a)综合解析,一文掌握所有要点!
- Chinese doctor Zhang Junqiao's heroic act exemplifies deep China-Africa friendship: FM spokesperson
- 用Python写了一个上课点名系统(附源码)(自制考勤系统)
- Kubernetes中的PV、PVC、Configmap介绍
- 标签列表
-
- ifneq (61)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)