代码精进之路
范学雷
前 Oracle 首席软件工程师,Java SE 安全组成员,OpenJDK 评审成员
38234 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 48 讲
结束语 (1讲)
代码精进之路
15
15
1.0x
00:00/00:00
登录|注册

10 | 异常处理都有哪些陷阱?

具体的异常要在接口规范中声明和标记清楚
异常的使用要符合具体的场景
不要使用异常机制处理正常业务逻辑
异常转换
异常堆栈
异常描述
异常类名
对于运行时异常,需要谨慎处理
对于检查型异常,编译器或者IDE会提醒使用合适的声明
应用程序需要处理异常
非运行时异常
运行时异常(RuntimeException)
非正常异常(Error)
正常状况和异常状况要分开处理
不应该使用异常机制处理正常状况
异常状况会降低代码效率
小结
处理好捕获异常
标记清楚抛出异常
分清异常的类别
异常就是非正常
异常处理陷阱
异常处理陷阱

该思维导图由 AI 生成,仅供参考

上一讲中我们聊了聊怎么用好 Java 注解,今天我们谈谈怎么处理异常。
处理好异常状况是掌握一门编程语言的基础,也是我们编程离不开的基本功。相信你对异常处理的机制已经很熟悉了。异常处理便捷、灵活、好用。但是,越好用的东西,我们越容易忽视它的缺陷。异常处理就有很多我们容易忽视的陷阱。今天,我们来聊聊这些问题,以及该怎么处理这些问题。

异常就是非正常

异常状况,就是不正常的状况。
异常状况的处理会让代码的效率变低。一个流畅的业务,它实现代码的执行路径,理想的状况就是没有任何异常状况发生。否则,业务执行的效率就会大打折扣。既然这样,我们就不应该使用异常机制来处理正常的状况。
这一点不难理解。可是,由于一门语言无法理解什么样的状况是正常状况,什么样的状况又是异常状况,也就无法限制异常机制使用的具体场景。所以作为程序员,我们需要自己解决好这个问题,不要滥用了异常机制。
比如说,很多 API 的设计有检查参数有效性的方法。如果参数通过检验,就没有异常抛出,否则就会抛出异常。在使用这个方法的代码时,我们需要检查有没有抛出异常来确认参数是否有效。
/**
* Check if the user name is a registered name.
*
* @throws IllegalArgumentException if the user name is invalid or
* not registered.
*/
void checkUserName(String userName) {
// snipped
}
这是一个糟糕的设计!
在这个例子中,如果 userName 字符串不符合规范,这是一个异常状况; 如果 userName 不是一个注册用户,这通常是一个正常状况。 在正常状况下使用异常处理,无疑会降低系统的效率,以及编码的效率。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

异常处理在编程中是一个基本功,但也存在许多陷阱。首先,需要分清正常状况和异常状况,避免滥用异常机制。其次,需要明确异常的类别,包括非正常异常、运行时异常和非运行时异常。对于运行时异常和非运行时异常,需要在方法的规范描述文档中清楚地标记异常,以便应用程序能够了解哪些异常需要处理、什么状况下会抛出异常以及该怎么处理这些异常。特别是对于运行时异常,需要特别注意标记清楚抛出异常,以避免降低代码效率和增加错误。因此,对于所有可能抛出运行时异常,都需要有清晰的描述,同时需要查看所有的调用方法的规范描述,确认抛出的异常要么已经处理,要么已经规范描述。这样能够有效提高编码和阅读代码的效率。 在处理异常时,需要了解异常机制的基本原理,包括异常类名、异常描述、异常堆栈和异常转换。这些要素能够帮助解决出错原因、出错位置和为何出错的问题。在编写规范的代码时,需要遵循三条准则:不要使用异常机制处理正常业务逻辑;异常的使用要符合具体的场景;具体的异常要在接口规范中声明和标记清楚。文章还提到了一段Java代码,让读者思考其中的异常处理是否违反了上述原则,并鼓励读者分享优化后的代码。 总的来说,本文强调了异常处理的重要性和注意事项,提供了对异常机制基本原理的深入理解,并鼓励读者在实践中不断优化异常处理的方式。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《代码精进之路》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(20)

  • 最新
  • 精选
  • Y024
    当方法中返回的数组或集合,会有为空的情况,不要直接返回 null(会强迫调用方需要进行判空处理,否则可能出现 NullPointerException),最好返回大小为 0 的数组或集合。 其实分配一个空数组或集合所花费的时间和空间,性能损坏是很小到基本可以忽略不计的。同时,如果返回的空数组或者集合是 immutable(即不可变的)的话,可以进一步定义成 static final(对于数组而言)或者 Collections.emptyList()/emptyMap()/emptySet(),来公用同一个对象,减少性能影响。

    作者回复: 这个留言必须赞!

    2019-01-30
    3
    49
  • 老杨同志
    1. 没找到结果应该是正常业务,不用抛出异常 2. 缺失必要的参数校验 import java.util.HashMap; import java.util.Map; class Solution { /** * Given an array of integers, return indices of the two numbers * such that they add up to a specific target. * return null if nums==null or nums.length==0 or result not found. */ public int[] twoSum(int[] nums, int target) { if(nums==null || nums.length==0){ return null; } Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int complement = target - nums[i]; if (map.containsKey(complement)) { return new int[] { map.get(complement), i }; } map.put(nums[i], i); } return null; } }

    作者回复: 改的很赞! 另外,有一个小技巧和你分享一下。如果返回值是空值(null),如果调用代码没有检查空值就调用,会抛除NullPointerException异常。如果返回空数组(int[0]), 就不会出现这个问题了。 这个小技巧可以减少调用代码的错误,这样设计的接口比较皮实耐用。如果返回值是数组或者集合,我们通常使用这一个技巧。

    2019-01-25
    13
  • 木白
    “ 对于异常类名,我们要准确地选择异常类。” 老师,我们应该怎么去快速准确地知道需要抛哪个异常呢?因为有时候只是觉得应该做异常检测,但是属于哪种异常自己也描述不清楚。需要把jdk中的异常都记下来吗?我就是那种直接抛Exception异常的。

    作者回复: 坏消息是,即使我们记住了所有的JDK异常类,也仅仅是异常世界的一小部分,不够用的。 好消息是,没有人能记住即便是JDK这么少的异常类,所有我们也不要折磨自己去记住所有的异常。 首先要记住,异常是一个可以扩展的类,需要时可以自己定义。所以,你的应用可以有自己定义的异常类。 弄不清楚使用哪种异常,有两种情况比较典型,一种是不了解调用的类;另一种是不了解自己编写的代码的逻辑。这两条清楚了,异常就清楚了。这两条不清楚,代码也很难清楚。 异常就不正常的状况。不正常的状况是什么,搞清楚了,异常该是什么也就知道了八九分。然后,去找合适的现存异常类,或者定义新的异常类,剩下的一两分工作也就凑齐了。 比如说,我回复这条信息的时候,总是输错字母。要是用异常表示,这个不正常的状况就是“键盘输入信息错误”。 然后,在你使用的类库中,去寻找有没有表示这个不正常状况的异常,找到了就用。找不到,如果你想精确定义,就自己定义一个KeyboadInputException; 如果你不想定义新异常,就扩大概念,从“键盘输入信息错误"扩大到"输入信息错误"。然后IOException就是大家都常用的异常了,然后你就可以使用new IOException("键盘输入信息错误")来表示“键盘输入信息错误”这个不正常状况了。 就这样,了解的代码,了解了不正常的状况到底是什么状况。

    2019-03-28
    7
  • 草原上的奔跑
    课后习题,1.使用异常处理正常的业务逻辑。2.异常没有在接口规范中标记清楚。3.异常类名感觉用在这里不合适

    作者回复: 找的都对。你想到怎么修改了吗?

    2019-01-25
    2
  • Demon.Lee
    老师,请教一个问题,在写定时任务的业务时,一开始从表中提取一些记录,然后针对每条记录进行业务处理(业务处理有事务),如果业务处理成功,则更新这条记录状态为A,如果业务处理失败,则更新这条记录状态为B,此时我就感觉违背了“不要用异常处理业务逻辑”这条规则,老师有什么好建议。另外,我想到的是用两个线程处理 ,主线程更新状态,子线程处理业务逻辑,但是需要线程间同步。 public void execute(){ List<T> rows = list(); for(T t:rows){ try{ deal(t); updateStatus("A"); }catch(Exception ex){ updateStatus("B"); } } }

    作者回复: deal()能不能不抛出异常?或者不正常的时候抛出异常,正常的业务不抛出异常? 使用线程处理更复杂,还不如捕获异常呢。

    2019-03-04
    2
    1
  • pyhhou
    思考题: 1. 对输入数组需进行预判 2. 方法内如果有异常抛出,需进行标记描述 3. 异常名称名不副实 import java.util.HashMap; import java.util.Map; class Solution { /** * Given an array of integers, return indices of the two numbers * such that they add up to a specific target. */ public int[] twoSum(int[] nums, int target) { if (nums == null || nums.length ==0) { return new int[0]; } Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int complement = target - nums[i]; if (map.containsKey(complement)) { return new int[] { map.get(complement), i }; } map.put(nums[i], i); } return new int[0]; } } 最后还想问下,如果说是 return 的不是 null 而是空数组,是不是就不需要在文档描述中标记写明? 年过了一半了,看看自己拉下的章节还有不少,得抓紧了,祝老师新的一年心想事成,身体健康,工作生活一切顺利~ 今年励志一定要好好和你学写代码,稳扎稳打,快速成长

    作者回复: 新春快乐! JDK的习惯是,不论是null还是空的集合、数组,都在文档描述中标记清楚。这样,调用者能确切地知道该怎么检查。

    2019-02-09
    1
  • 进化菌
    想起我们以前抛异常常用的话术“服务器繁忙,请稍后重试”,服务器是够繁忙的,光是异常就变化万千。 不过,我们还是得和异常和谐相处~ 异常处理的三条准则: 不要使用异常机制处理正常业务逻辑; 异常的使用要符合具体的场景; 具体的异常要在接口规范中声明和标记清楚。

    作者回复: 第一次听说这样的话术,有意思!

    2021-11-19
  • 北风一叶
    异常的使用要符合具体的场景,这一条非常虚,不具备可执行性

    作者回复: 哈哈,看来你喜欢干脆的结论。

    2019-03-08
  • 李星
    如果是异常没有被try-catch住的线程呢?

    作者回复: 我们先来看看线程的主方法声明“public void run()”。这个声明没有抛出检查型异常,只能抛出运行时异常。所以,检查型异常一定要在线程的实现中得到处理;否则的话,编译器应该报错的。这个方法可以抛出运行时异常。一个线程,像一个普通的方法一样(run()),抛出运行时异常后,线程就终止了。问题在于,线程通常共享资源,如果线程之间有联系,很多事情就会发生,依赖于线程的具体实现逻辑。比如说,如果一个线程要等待另一个线程的I/O,也许会阻塞。

    2019-02-12
  • 李星
    想问一下作者,在多线程情况下时,当某一个线程发生运行时异常,并且不处理时,是否真的会阻塞当前线程呢?使得这个线程被废掉。?

    作者回复: 当前线程是发生异常没处理的线程吗? 还是不同的两个线程? 没太明白问题。

    2019-02-12
收起评论
显示
设置
留言
20
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部