[读书笔记]《Effective Java》第9章异常

第57条:只针对异常的情况才使用异常

  • 异常机制是用于不正常的情形,很少会有JVM实现试图对它们进行优化。在现代的JVM实现上,基于异常的模式比标准模式要慢得多。
  • 把代码放在try-catch块中反而阻止了现代JVM实现本来可能执行的某些特定优化。
  • 设计良好的API不应该强迫它的客户端为了正常的控制流而使用异常。如果类具有“状态相关”的方法,即只有在特定的不可预知的条件下才可以被调用的方法,这个类往往也应该有个单独的“状态测试”方法,即指示是否可以调用这个状态相关的方法。例如,Iterator接口有一个“状态相关”的next方法,和相应的状态测试方法hasNext。另一种做法是如果对象处于不适当的状态,调用“状态相关”的方法返回一个可识别的值,比如null。
  • 选择关于“状态测试方法”和“可识别的返回值”两种做的原则。如果访问者没有做同步,对象会被并发访问的情况下,选择“可识别的返回值”,因为调用“状态测试方法”和“状态相关”的方法之间会有时间间隔。如果其他方面都等同,选择“状态测试方法”,因为有更好的可读性,对于使用不当的情形,“状态相关”的方法会抛出异常,便于检测和修正错误。

第58条:对可恢复的情况使用受检异常,对编程错误使用运行时异常

  • Java程序设计语言提供了三种可抛出结构:受检异常、运行时异常和错误。运行时异常和错误都属于未受检异常。
  • 使用受检异常或是未受检异常的原则:
  1. 如果期望调用者能够适当地恢复,使用受检的异常。
  2. 用运行时异常来表明编程错误。
  • 你实现的所有未受检的抛出结构都应该是RuntimeException的子类(直接的或间接的)。因为错误被JVM保留用于表示资源不足、约束失败,或者其他使程序无法继续执行的条件。
  • 可以定义一个抛出结构,它不是Exception、RuntimeException或Error的子类。从行为意义上讲它们等同于普通的受检异常(即Exception的子类,而不是RuntimeException的子类)。但是不要定义这样的类,没什么好处,只会困扰用户。

第59条:避免不必要地使用受检的异常

  • 如果方法抛出一个或者多个受检的异常,调用该方法的代码就必须在一个或者多个catch块中处理异常,或者它必须声明它抛出异常,并让它们传播出去。无论哪种方法,都给程序员增添了不可忽视的负担。
  • 如果正确地使用API并不能阻止这种异常条件的产生,并且一旦产生异常,使用API的程序员可以立即采取有用的动作,则使用受检异常,否则更适合于使用未受检的异常。
  • 把受检异常变成未受检异常的一种方法,把这个抛出异常的方法分成两个方法,其中第一个方法返回一个boolean,表明是否应该抛出异常。这个方法同第57条的“状态测试方法”,“状态测试方法”存在的问题(并发访问)在这里也同样存在。

第60条:优先使用标准的异常

  • 重用现有异常的好处:
  1. 使你的API更易于学习和使用。
  2. 程序可读性好,因为不会出现很多程序员不熟悉的异常。
  3. 异常类越少,意味着内存印迹就越小,装载这些类的时间开销也越少。
  • 常见的可重用异常
异常类型 使用场合
IllegalArgumentException 非null的参数值不正确
IllegalStateExcepition 对于方法调用而言,对象状态不适合
NullPointerException 在禁止使用null的情况下使用null
IndexOutOfBoundsException 下标参数值越界
ConcurrentModificationException 在禁止并发修改的情况下,检测到并发修改
UnSupportedOperationException 对象不支持用户请求的方法

第61条:抛出与抽象相对应的异常

  • 高层方法调用低层方法,如果低层方法抛出异常而高层方法直接将低异常抛出,这个异常可能和高层方法没有什么联系,会让人不知所措。如果低层方法修改了抛出的异常,导致高层也被修改了,会破坏现有的客户端。
  • 更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常。这种做法被称为异常转译。如下:
// 异常转译
try {
     // 调用低层代码
} catch (LowerLevelException e) {
     throw new HigherLevelException();
}
  • 特殊的异常转译形式:异常链。低层的异常原因被传到高层异常,高层的异常提供访问方法(Throwable.getCause)来获得低层的异常。如下:
// 异常链
try {
     // 调用低层代码
} catch (LowerLevelException e) {
     throw new HigherLevelException(e);
}

例:

 1 /**
 2  * 高层异常,有接受异常的构造器
 3  * Created by itlivemore on 2017/6/25.
 4  */
 5 class HigherLevelException extends Exception {
 6     public HigherLevelException() {
 7     }
 8
 9     // 接受异常的构造器
10     public HigherLevelException(Throwable cause) {
11         super(cause);
12     }
13 }
14
15 /**
16  * 高层异常,没有接受异常的构造器
17  */
18 class HigherLevelException2 extends Exception {
19 }
20
21 /*低层异常*/
22 class LowerLevelException extends Exception {
23     public LowerLevelException(String message) {
24         super(message);
25     }
26 }
27
28 class Lower {
29     static void f(int param) throws LowerLevelException {
30         throw new LowerLevelException("参数" + param + "调用f()异常");
31     }
32 }
33
34 /**
35  * 异常链
36  * Created by itlivemore 2017/6/25.
37  */
38 public class ExceptionChain {
39     public static void main(String[] args) {
40         try {
41             call1();
42         } catch (HigherLevelException e) {
43             e.printStackTrace();
44         }
45         try {
46             call2();
47         } catch (HigherLevelException2 e) {
48             e.printStackTrace();
49         }
50     }
51
52     private static void call1() throws HigherLevelException {
53         // 有运行异常链的构造器,则将异常放到构造方法中
54         try {
55             Lower.f(1);
56         } catch (LowerLevelException e) {
57             throw new HigherLevelException(e);
58         }
59     }
60
61     private static void call2() throws HigherLevelException2 {
62         // 没有运行异常链的构造器,使用initCause()设置原因
63         try {
64             Lower.f(2);
65         } catch (LowerLevelException e) {
66             HigherLevelException2 exception = new HigherLevelException2();
67             exception.initCause(e);
68             throw exception;
69         }
70     }
71 }
  • 处理低层异常的最好方式是避免抛出异常,如高层在调用低层前检查参数来保证低层执行成功。如果不能避免异常,高层可以绕开异常,仅用日志记录异常,用于排查问题。

第62条:每个方法抛出的异常都要有文档

  • 始终要单独地声明受检的异常,并且利用Javadoc的@throws标记,准确记录下抛出每个异常的条件。一个方法可能抛出多个异常,不要声明为"throws Exception"。
  • 对于未受检异常,在@throws标签中记录,不要在方法的throws中声明。
  • 如果一个类中的许多方法出于同样的原因而抛出同一个异常,可以在该类的文档注释中对这个异常建立文档。如输入参数为空,都抛出NullPointerException。

第63条:在细节消息中包含能捕获失败的消息

  • 程序由于未被捕获的异常运行失败时,系统会打印异常栈轨迹,会调用异常的toString方法,所以异常的toString方法要尽可能地返回有关失败原因的信息。
  • 为了捕获失败,异常的细节信息应该包含所有“对该异常有贡献”的参数和域的值。
  • 为了确保在异常的细节消息中包含足够的能捕获失败的信息,一种办法是在异常的构造器而不是字符串细节消息中引入这些消息。如IndexOutOfBoundsException有 public IndexOutOfBoundsException(int lowerBound,int upperBound,int index) 这样的构造器。

第64条:努力使失败保持原子性

  • 失败原子性:失败的方法调用应该使对象保持在被调用之前的状态。
  • 实现失败原子性的方法
  1. 设计一个不可变对象。如果对象是不可变的,失败的原子性就是显然的。
  2. 执行操作之前检查参数的有效性。
  3. 调整计算处理过程的顺序,使得任何可能会失败的计算部分都在对象状态被修改之前发生。
  4. 不常用的做法,主要用于永久性的(基于磁盘的)数据结构。做法是编写一段恢复代码,由它来拦截操作过程中发生的失败,以及使对象回滚到操作开始之前的状态。
  5. 在对象的一份临时拷贝上执行操作,当操作完成之后再用临时拷贝中的结果代替对象的内容。
  • 不利于实现失败原子性的情况:
  1. 多线程且没有适当同步机制的情况下,并发地修改同一个对象。
  2. 对于某些操作,会显著地增加开销或者复杂性。
  • 产生任何异常都应该让对象保持在该方法调用之前的状态。如果违反这条规则,API文档应该清楚地指明对象会处于什么样的状态。

第65条:不要忽略异常

  • 忽略异常的方法:将方法调通过try块包围起来,并包含一个空的catch块
// 忽略异常
try {
// 调用方法
} catch (Exception e) {
}
  1. 如果能够忽略异常,catch块中应该包含一条说明,解释为什么可以忽略这个异常。
  2. 不要忽略异常,把异常记录下来是明智的做法。
时间: 2024-10-11 16:58:47

[读书笔记]《Effective Java》第9章异常的相关文章

[读书笔记]Effective Java 第四章

使类和成员的可访问性最小化 规则很简单:尽可能地使每个类或者成员不被外界访问.实例域(非final)决不能是公有的.当需要暴露出不可变的实例时通常会把这个实例做成不可变或者是把这个实例变成私有,同时提供该实例的备份. 在公有类中使用访问方法而非公有域 这就是常说的getter和setter方法,提供给包外访问时提供必要的方法,限制客户端的行为,以便于将来可以在内部改变表示方法. 使可变性最小化 不可变的类比可变类更加易于设计.实现和使用.它们不容易出错,且更加安全.为了使类成为不可变,要遵循下面

[读书笔记]Effective Java 第三章

覆盖equals方法时请遵守通用约定 这种说法的大意是要说明,Object类中定义的很多默认准则是被许多工具类或是第三方框架共同遵守的标准,一旦改动这样的标准机制,会在后续的使用中产生不稳定因素.equals方法常见用来做以下用途时,不建议对equals方法进行覆盖: 1.判断实例的唯一性 2.提供某种程度的逻辑相等 equals方法满足自反性,对称性,传递性,一致性,非空性.当需要覆盖equals方法时,需要注意以下三点: 1.覆盖equals时总要覆盖hashCode 2.不要企图让equa

[读书笔记]Effective Java 第一章

需要了解JAVA最近每个版本新增的特性,并善用这些新特性为自己的程序实现高效简洁的代码. 其中提到的编程原则包括: 模块要尽可能的小 代码应该要被重用,而不是被拷贝 模块之间的依赖性应该尽可能降到最小 错误应该尽早被检测,最好是在编译期

[读书笔记]Effective Java 第二章

考虑用静态工厂方法代替构造器 静态工厂方法第一大优势是可以用特有的名称.常见的类创建实例需要用构造函数,而当需要两个构造函数时,我们需要在参数列表做区分才能写出两个构造方法.使用静态工厂模式,可以规避方法名=类名的限制,使用更易读的方法呈现. 静态工厂方法第二大优势是不必在每次调用的时候创建一个新的实例.这点和单例设计模式有些类似,在使用不可变类的时候可以预先构建实例并缓存,之后可以重复利用,避免创建不必要的重复对象,也可以用静态工厂保证这是一个单例.可以确保不会存在两个相等的实例,即当且仅当a

【读书笔记 - Effective Java】02. 遇到多个构造器参数时要考虑用构建器

类有多个可选参数的解决方案:1. 重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读.2. JavaBeans模式,调用一个无参构造器来创造对象,然后调用setter方法来设置每个必要的参数,以及每个相关的可选参数. 缺点:构造过程被分到了几个调用中,在构造过程中JavaBean可能处于不一致的状态.阻止了把类做成不可变的可能,需要程序员确保线程安全.3. Builder模式,模拟了具名的可选参数. 模式 优 劣 重叠构造器 写法最简单 多参数时候难读.难写.难

【读书笔记 - Effective Java】01. 考虑用静态工厂方法代替构造器

获取类的实例有两种方法: 1. 提供一个公有的构造器(最常用). 2. 提供一个公有的静态工厂方法(static factory method). // 静态工厂方法示例 public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; } 静态工厂方法的优势: 1. 有名称.当一个类需要多个带有相同签名(方法签名 = 方法名 + 参数列表)的构造器时,就用静态工厂方法代替构造器,并且慎重选择名

【java读书笔记】——java的异常处理

程序在实际环境的运行过程中,安全成为需要首先考虑的重要因素之一,这也是用户和程序员最关心的问题.同时,Java语言健壮性也体现在了能够及时有效地处理程序中的错误,准确的说是Java的异常处理机制为解决安全问题提交了一份满意的答卷. 先从整体上看一下本文要介绍的内容,然后进行详细的分析: 1.异常的概念 异常又称为例外,是特殊的运行错误对象,对应着Java语言特定的运行错误处理机制. 上面的概念是书本上说的,下面我谈一下自己的看法 其实异常处理机制提供了统一的机制来识别和响应程序错误,异常可以为我

Java核心技术 卷1 读书笔记 (3 Java基本程序设计结构)

3.3 数据类型 Java是强类型语言,必须为每一个变量声明一种类型. 3.3.1 整型 Java 提供四种整型 int 4字节 short 2字节 long 8字节 byte 1字节 长整型(long)数值有一个后缀L(例如40000000000L),十六进制数值有一个前缀0x(例如0xCAFE),八进制数值有一个前缀0(例如010). 3.3.2 浮点型 Java提供两种浮点类型 float 4字节 double 8字节 float类型的数值有一个后缀F(例如3.42F),没有后缀F的浮点数

【读书笔记】设计模式第五章:行为型模式

本文主要分析了模板方法模式.命令模式.责任链模式.策略模式.迭代器模式,介绍它们的定义.优缺点.使用场景,以及实例代码.为了深刻地理解设计模式,最重要的还是动手编写代码. 我参照书中的例程重新构想了一些更加生动.易于理解的例子,希望大家喜欢. 代码可以通过以下链接进行浏览: http://git.oschina.net/caipeichao/java-design-pattern 这些代码都经过编译运行,保证没有错误. 模板方法 定义 定义一个操作中的算法框架,而将一些步骤延迟到子类中 角色:抽

[读书笔记]算法(Sedgewick著)·第一章(1)

到家放松之后就开始学习算法了,手里拿的是拿的是一本Robert Sedgewick的橙皮书<算法(第四版)>的.这本书与导论那本书的不同之处在于轻数学思想.重实现,也就是说这是一本很不错的基础编程书.拿来做书中的练习还是蛮不错的,封面说有50种算法哦.思维导图如下,就且学且更新吧. 1.基本编程模型 第一章开始讲述用程序实现算法的优点:程序是对算法精确.优雅和完全的描述:可以通过运行程序来学习算法的各种性质:可以在应用程序中直接使用这些算法.还有这种学习算法的缺点缺点:分离思想和实现细节的困难