Tips

书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code

注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。

异常

当充分发挥异常的优势时,它可以提高程序的可读性、可靠性和可维护性。如果使用不当,则会产生相反的效果。本章提供了有效使用异常的指南。

69. 仅在发生异常的条件下使用异常

有一天,如果你运气不好,你可能会偶然发现这样一段代码:

// Horrible abuse of exceptions. Don't ever do this!
try {
int i = 0;
while(true)
range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {
}

这段代码是做什么的?检查结果看来一点也不明显,这就是不使用它的充分理由(条目 67)。事实证明,这是一种用于循环遍历数组元素的非常错误的习惯用法。当试图访问数组边界之外的第一个数组元素时,无限循环通过抛出、捕获和忽略ArrayIndexOutOfBoundsException异常来终止。它应该等同于循环数组的标准习惯用法,任何Java程序员都可以一眼就能识别出来:

for (Mountain m : range)
m.climb();

那么为什么有人会使用基于异常的循环而不是尝试和正确的用法? 根据错误推理提高性能是一种错误的尝试,因为虚拟机检查所有数组访问的边界,由编译器隐藏但仍然存在于for-each循环中的正常循环终止测试是多余的,应该避免。 这个推理有三个问题:

  • 因为异常是为特殊情况设计的,所以JVM实现者几乎没有试图让它们像显式测试一样快。
  • 将代码放在try-catch块中会抑制虚拟机实现可能执行的某些优化。
  • 遍历数组的标准习惯用法不一定会导致冗余检查。许多虚拟机实现对它们进行了优化。

事实上,基于异常的习惯用法比标准用法慢得多。在我的机器上,100个元素的数组,基于异常的习惯用法的速度大约是标准习惯用法的两倍。

基于异常的循环不仅混淆了代码的目的,降低了代码的性能,而且不能保证它能正常工作。如果循环中存在bug,使用异常进行流控制可以掩盖该bug,从而大大增加调试过程的复杂性。假设循环体中的计算调用一个方法,该方法对一些不相关的数组执行越界访问。如果使用合理的循环习惯用法,该bug将生成一个未捕获的异常,导致线程立即终止,并带有完整的堆栈跟踪。如果使用错误的基于异常的循环,则会捕获与bug相关的异常,并将其误解为正常的循环终止。

这个示例说明的道理很简单:顾名思义,异常仅用于特殊情况; 它们永远不应该用于正常的控制流程。 通常来说,使用标准的、易于识别的习惯用法,而不是声称可以提供更好性能的过度聪明的技术。即使性能优势是真实存在的,但在稳步改进平台实现的情况下,这种优势也可能不复存在。然而,来自过度聪明的技术的细微缺陷和维护难题肯定会继续存在。

这个原则对API设计也有影响。一个设计良好的API不能强迫它的客户端为正常的控制流使用异常。只有在某些不可预知的条件下才能调用具有“状态依赖(state-dependent)”方法的类,通常应该有一个单独的“状态测试(state-testing)”方法,指示是否适合调用状态依赖方法。例如,Iterator接口具有依赖于状态的next方法和对应的状态测试方法hasNext。这支持使用传统for循环(以及for-each循环,其中内部使用了hasNext方法)在集合上迭代的标准习惯用法:

for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) {
Foo foo = i.next();
...
}

如果Iterator缺少hasNext方法,则客户端将被迫执行此操作:

// Do not use this hideous code for iteration over a collection!
try {
Iterator<Foo> i = collection.iterator();
while(true) {
Foo foo = i.next();
...
}
} catch (NoSuchElementException e) {
}

这数组迭代的例子非常类似于本条目一开始的那个例子。除了冗长和误导之外,基于异常的循环很可能执行得很差,并且可以掩盖系统中不相关部分中的bug。

提供单独的状态测试方法的另一种方式是,让依赖于状态的方法返回一个空的Optional值(条目 55),或者在它不能执行所需的计算时返回一个区分值,比如null。

下面是一些指导原则,帮助你在状态测试方法,Optional的或区分的返回值之间进行选择。如果要在没有外部同步的情况下并发地访问对象,或者受制于外部引发的状态转换,则必须使用Optional的或可区分的返回值,因为在调用状态测试方法与其依赖于状态的方法之间的间隔内,对象的状态可能会发生变化。如果一个单独的状态测试方法将重复依赖于状态的方法的工作,那么性能问题可能要求使用一个Optional的或可区分的返回值。在所有其他条件相同的情况下,状态测试方法略优于区分的返回值。它提供了更好的可读性,而且不正确的使用可能更容易检测:如果忘记调用状态测试方法,依赖于状态的方法将抛出异常,使错误变得明显;如果忘记检查一个可区分的返回值,那么这个bug可能很微妙。这不是Optional返回值的问题。

总之,异常是针对特殊情况而设计的。不要将它们用于正常的控制流程,也不要编写强制其他人这样做的API。

Effective Java 第三版——69. 仅在发生异常的条件下使用异常的更多相关文章

  1. 《Effective Java 第三版》目录汇总

    经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...

  2. 《Effective Java 第三版》新条目介绍

    版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...

  3. Effective Java 第三版——22. 接口仅用来定义类型

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  4. Effective Java 第三版——3. 使用私有构造方法或枚类实现Singleton属性

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  5. Effective Java 第三版——7. 消除过期的对象引用

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  6. Effective Java 第三版——10. 重写equals方法时遵守通用约定

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  7. Effective Java 第三版——11. 重写equals方法时同时也要重写hashcode方法

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  8. Effective Java 第三版——18. 组合优于继承

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  9. Effective Java 第三版—— 20. 接口优于抽象类

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

随机推荐

  1. python中sqlite问题和坑

    import sqlite3 #导入模块 conn = sqlite3.connect('example.db') C=conn.cursor() #创建表 C.execute('''CREATE T ...

  2. groupadd 创建组

    groupadd 创建组 1 注意 :root用户才有权使用这个命令 2 groupadd -g 744 cjh  指定组ID号 3 在/etc/passwd 产生一个 组ID GID gpasswd ...

  3. 第二章 《C++ Primer Plus》

    第二章: 让窗口暂停: cin.get(); 作为接口的函数头: 函数头句法: 函数头描述了函数与调用它的函数之间的接口. int main(); int main(void); return 0; ...

  4. 潭州课堂25班:Ph201805201 django 项目 第四十三课 后台 用户管理前后功能实现 (课堂笔记)

    用户的展示,编辑,删除, 把用户显示出来,用户名,员工(是,否), 超级用户(是, 否) 活跃状态,(非活跃示为删除) 在前台要显示该用户所属的用户组,在前台代码中是调用类的属性,所以在 user 的 ...

  5. 什么是C++

    1.到底什么是C++ C++是一门面向对象的程序设计语言. 关键词:程序设计语言,面向对象 程序设计语言是用来书写计算机程序的语言,更形象的说,程序设计语言是用来和计算机“交流的语言.” 面向对象(o ...

  6. spring 空指针报错,Could not create connection to database server.

    驱动问题,换成最近版本的mysql驱动

  7. Unable to find utility "instruments", not a developer tool or in PATH

    在调试ios上的项目的时候出现报错 unable to find utility "instruments", not a developer tool or in PATH 报错 ...

  8. (转)JavaWeb学习之Servlet(一)----MyEclipse及Tomcat的配置

    [声明] 欢迎转载,但请保留文章原始出处→_→ 文章来源:http://www.cnblogs.com/smyhvae/p/4134921.html [开发环境] 物理机版本:Win 7旗舰版(64位 ...

  9. 天天爱跑步 [NOIP2016]

    Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务.这个游戏的地图可 ...

  10. 3ds max学习笔记(六)-- 基本操作(建模前奏)

    1.界面设置 在3ds Max的版本的界面中,默认是较深.若需要切换至较亮的界面,步骤: 执行“自定义”菜单,选择“加载自定义用户界面方案”从弹出的界面中选择样式文件,单击“打开”即可: 注:“amg ...