关于Java异常处理的9条原则
关于Java异常处理的9条原则
在Java编程中,合理有效地处理异常对于保证程序的稳定性和可维护性至关重要
充分发挥异常优点,可以提高程序可读、可靠、可维护性
本文基于Effective Java 异常章节总结9条异常处理原则
<img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/237c3b8ffc6b422b885282880ee4bbe2~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1335&h=1080&s=170951&e=jpg&b=ffffff" alt="" width="100%" />
只针对异常情况才使用异常
不要使用异常来做程序的流程控制,只有针对异常情况才使用异常
不主动判断数组下标是否越界,而使用异常控制流程的反例:
int[] ints = {1, 2, 3, 4, 5}; int index = 0; try { while (true) { System.out.println(ints[index++]); } } catch (ArrayIndexOutOfBoundsException e) { }
对可恢复的情况使用受检异常,对编程错误使用运行时异常
广泛的异常指Throwable,它可以分为三种异常:
- 受检异常 CheckException:编译时需要处理(捕获/抛出)的异常(比如IOException等)
- 运行时异常 RuntimeException:程序运行错误时抛出的异常(比如空指针NullPointerException、非法参数等)
- 错误 Error:运行时虚拟机出现的错误(比如OOM等)
处理受检异常时可以捕获或抛出进行处理,如果希望“恢复”则可以在捕获时进行重试
如果要自定义未受检异常(编译时不需要处理),则要为运行时异常的子类
class MyException extends RuntimeException
错误一般不在代码中进行处理,发生错误时需要排查根源再改造代码
API设计时遵循:对于可以恢复的情况抛出受检异常、对于程序错误抛出运行时异常、不确定能不能恢复抛出未受检异常 (未受检异常可以看成运行时异常)
如果在最外层(离用户最近)返回用户能理解的错误信息
避免不必要的使用受检异常
受检异常需要手动进行处理,这往往能够带来可靠
但是多种受检异常会让API难以使用,调用者处理时直接痛苦面具~
try { } catch (SQLException se) { // 处理数据库相关异常 } catch (IOException ioe) { // 处理文件读写相关异常 } catch (ClassNotFoundException cnfe) { // 处理类未找到异常,可能在加载驱动时出现 }
这时候为了偷懒可能会直接使用Exception统一进行处理~
try { } catch (Exception e) { // 偷懒 }
如果不使用catch处理就直接抛出受检异常
如果无法恢复则抛出未受检异常,通常是自定义的业务异常,如调用失败请稍后重试
记得带上异常信息,防止后续打印日志导致异常信息丢失
try { } catch (IOException e) { throw new MyException("请稍后重试", e); }
优先使用标准的异常
优先复用标准异常,如非法参数、数组下标越界异常
业务开发更多的还是复用自定义的业务异常~
复用已有的异常,不满足再自定义新异常
抛出与抽象对应的异常
当设计抽象层次的方法时,关注抽象层级的异常,而不是底层的具体实现的异常
ArrayList的迭代器在获取下一个元素时,如果越界会抛出NoSuchElementException
public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; }
AbstractSequentialList 在实现get获取元素时捕获NoSuchElementException,抛出IndexOutOfBoundsException
public E get(int index) { try { return listIterator(index).next(); } catch (NoSuchElementException exc) { throw new IndexOutOfBoundsException("Index: "+index); } }
抽象层次捕获实现层次的NoSuchElementException异常,并抛出按照抽象层次进行解释的异常IndexOutOfBoundsException
每个方法抛出的所有异常要建立文档
如果方法要抛出异常,在文档中使用@throw说明什么情况下会抛出该异常
/** * @throws IOException * if an I/O error occurs */
避免抛出Exception、Throwable 要抛出更具体的异常
方法的throws只说明可能抛出的受检异常
@throw要记录在哪种情况下可能抛出的受检异常和运行时异常
在异常信息中保留关键信息
异常中会存储字符串保留当时发生异常的现场相关信息,这种信息对于我们的排查是非常有利的
为了能够更容易的保留这种关键信息,可以在自定义异常时写出方便排查的构造
public class IndexOutOfBoundsException extends RuntimeException { //下限 private final int lowerBound; //上限 private final int upperBound; //当前越界下标 private final int index; public IndexOutOfBoundsException(int lowerBound, int upperBound, int index) { //关键信息 super(String.format( "Lower bound: %d, Upper bound: %d, Index: %d", lowerBound, upperBound, index)); this.lowerBound = lowerBound; this.upperBound = upperBound; this.index = index; } }
比如这个下标越界中包含上下界限以及当前下标位置,能够给出关键信息
努力使失败保持原子性
有些情况下发生异常导致失败会让对象的状态不一致,从而导致数据不一致
发生这种情况后,如果再使用数据不一致的对象就会发生错误
在实现方法时应该努力让发生异常导致失败时保持原子性,失败的调用方法应该让对象处于之前的状态
保证原子性的方法有5种:
-
使用不可变对象:即使失败导致出错只要不创建/替换对象,对象都是不可变的
-
使用前检查入参,提前抛出异常
比如ArrayList.remove方法,获取下标前要检查入参
public E remove(int index) { rangeCheck(index); modCount++; //如果不检查入参,此时抛出下标越界异常导致modCount数据不一致 E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; return oldValue; }
-
调整处理顺序,让可能导致程序失败的步骤发生在改变数据操作之前(类似第二种)
比如TreeSet需要内部元素实现比较器,如果未实现比较器或者元素类型不同,会发生类型转换异常,从而抛出异常不会执行添加操作
-
将源对象进行拷贝,如果发生异常错误可以找回源对象(或直接使用拷贝的对象进行处理)
列表排序时会先拷贝一份数组再进行排序
default void sort(Comparator<? super E> c) { //拷贝 Object[] a = this.toArray(); Arrays.sort(a, (Comparator) c); //排序完设置 ListIterator<E> i = this.listIterator(); for (Object e : a) { i.next(); i.set((E) e); } }
-
提供回滚操作,发生异常错误时使用回滚操作达到对象的状态一致
不要忽略异常
发生异常时不要忽略(catch块为空)
try{ }catch{ //为空 忽略 }
忽略异常会导致程序继续执行下去可能导致错误发生,错误发生时也会难以排查
处理异常时可以打印日志,保留异常堆栈信息,如果要抛出就不要重复打印日志
如果要忽略可以写下注释说明理由
总结
只有针对异常情况才使用异常,不要使用异常来做程序的流程控制
广泛的异常分为受检异常、运行时异常(非受检异常)和错误,通常只接触前两者,后者排查虚拟机错误时才接触
对于运行恢复的情况抛出受检异常,程序错误或不确定是否允许恢复的情况抛出运行时异常
受检异常必须进行处理,能够带来可靠,但太多会导致复杂,不catch处理受检异常时可以直接抛出
优先复用已有的标准异常,不满足需求时再自定义
设计抽象层次方法时,关注抽象层次异常,而不是具体实现异常,通过捕获具体实现异常再抛出抽象层次异常
方法文档需要说明可能抛出的异常,不要抛出Exception异常,要抛出具体异常
自定义异常时尽量构造出方便排查的关键信息
异常失败可能导致对象状态不一致,可使用不可变对象、检查入参、调整执行顺序、拷贝对象、实现回滚等方案解决
忽略异常会导致程序继续执行从而发生错误结果,难以排查
最后(不要白嫖,一键三连求求拉~)
本篇文章被收入专栏 Effective Java,感兴趣的同学可以持续关注喔
本篇文章笔记以及案例被收入 Gitee-CaiCaiJava、 Github-CaiCaiJava 感兴趣的同学可以stat下持续关注喔~
有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~
关注菜菜,分享更多干货,公众号:菜菜的后端私房菜
本文由博客一文多发平台 OpenWrite 发布!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
玩转OurBMC第六期:OpenBMC之传感器配置及使用
栏目介绍:“玩转OurBMC”是OurBMC社区开创的知识分享类栏目,主要聚焦于社区和BMC全栈技术相关基础知识的分享,全方位涵盖了从理论原理到实践操作的知识传递。OurBMC社区将通过“玩转OurBMC”栏目,帮助开发者们深入了解到社区文化、理念及特色,增进开发者对BMC全栈技术的理解。 欢迎各位关注“玩转OurBMC”栏目,共同探索OurBMC社区的精彩世界。同时,我们诚挚地邀请各位开发者向“玩转OurBMC”栏目投稿,共同学习进步,将栏目打造成为汇聚智慧、激发创意的知识园地。 OpenBMC的传感器配置与使用是实现系统全面监控和优化的关键环节,对于提高系统的稳定性和可靠性至关重要。本期内容将重点探讨 OpenBMC 的传感器配置与使用。首先,我们将深入解析 OpenBMC 传感器的工作流程,详细探讨 OpenBMC 比较主流的传感器配置方式和使用方法—— entity-manager 与 dbus-sensors,以便于读者更好地理解 OpenBMC 传感器的运作机制,并掌握其配置与使用技巧。 OpenBMC传感器监控 在服务器硬件管理中,传感器监控起着至关重要的作用。传感器监控...
- 下一篇
1个基础模型系列、3大 AI 开发工具,Create 2024重磅发布都在这里了!
4月16日,百度举办了 Create 2024百度 AI 开发者大会,包括百度创始人、董事长兼首席执行官李彦宏在内的多位重磅嘉宾登台演讲,并与全球各地的开发者们分享了百度在 AI 领域的最新技术进展。 人人都是开发者 百度创始人、董事长兼首席执行官李彦宏以“人人都是开发者”为题发表演讲,为全球开发者带来了一个强大的基础模型系列和三大 AI 开发工具,并将它们组成了一个工具箱,支持开发者打包带走,随取随用。李彦宏表示,未来,自然语言将成为新的通用编程语言,你只要会说话,就可以成为一名开发者,用自己的创造力改变世界。 强大的基础模型系列,也就是文心大模型系列,包括了:旗舰版的 ERNIE 3.5和4.0,和轻量版的 ERNIE Speed、Lite、Tiny 等;现场,李彦宏还正式发布了文心大模型4.0的工具版。文心一言发布一年多以来,用户数已经突破2亿,目前,文心大模型已成为中国最领先、应用最广泛的 AI 基础模型。 三大“开箱即用”的 AI 开发工具,包括智能体开发工具 AgentBuilder、AI原生应用开发工具 AppBuilder、各种尺寸的模型定制工具 ModelBuild...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- 设置Eclipse缩进为4个空格,增强代码规范
- Red5直播服务器,属于Java语言的直播服务器
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS8编译安装MySQL8.0.19
- CentOS关闭SELinux安全模块
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作