《Effective Java》读书笔记 - 9.异常
Chapter 9 Exceptions
Item 57: Use exceptions only for exceptional conditions
这条item的意思就是,千万不要用exception来控制control flow的终止,比如:
// Horrible abuse of exceptions. Don't ever do this!
try {
int i = 0;
while(true)
range[i++].climb();
} catch(ArrayIndexOutOfBoundsException e) {
}
这段代码用异常来终止循环,这根本就不是异常的归宿。异常仅仅是为了exceptional conditions而存在的。此外,上面这种做法的坏处一大堆,根本列举不完。
一个好的API设计不会强制client用异常来完成控制流的终止,比如Iterator只有next()而没有hasNext()的话,那么由于next()可能会抛出异常所以client不得不catch住才知道没有元素可以拿了。
你也可以通过返回一个特殊的值(比如null)来告诉client:有问题啦!如果想让client在不用同步的情况下,并发地访问你的类,那么就可以用这种方法,因为如果用刚才那种“先检查,再调用”的方法的话,可能“先检查”完之后,状态被另一个线程改变了,那么“再调用”的时候还是可能抛出异常。除了这种情况外,大部分情况下都应该用“先检查,再调用”的方法(因为会让你的错误用法很容易被发现,比如你忘了先检查,那么就会抛出异常告诉你你代码有问题)。
Item 58: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors
有三种Throwable,分别是checked exceptions,runtime exceptions,和errors。checked exceptions一般用于可以恢复的情况。对于一个声明了throws的API方法,类库实现者强制client要处理这个异常。而另外两种Throwable一般不需要被catch,如果一个程序抛出了一个这样的非checked的异常,那么通常是不可恢复的,否则可能do more harm than good。runtime exceptions一般代表程序错误(代码写的有问题),比如ArrayIndexOutOfBoundsException。而errors,按照惯例,是被JVM保留用于表示资源不足等 使程序无法继续执行的异常。所以你不应该继承Error这个类,所以你自己实现的所有的unchecked异常都应该是继承自RuntimeException这个类。上面都用了“一般”这个词,因为情况并不是绝对的,比如有时候资源耗尽可能是由于你的程序错误导致的。在自定义异常中,你可以定义各种方法,从而给catch到这个异常的代码提供更多的信息。
Item 59: Avoid unnecessary use of checked exceptions
有时候定义一些不必要的checked exception会给client带来额外的负担,因为你强制他们要处理你的异常。如果说client遇到了这个unchecked exception最多只能作如下处理:
} catch(TheCheckedException e) {
throw new AssertionError(); // 不可能发生!
}
或者:
} catch(TheCheckedException e) {
e.printStackTrace(); // Oh well, we lose.
System.exit(1);
}
那么你就可以考虑是不是可以把你的checked异常变成unchecked了。一种技巧就是把你的方法拆成两个方法,一个负责检查,另一个抛出一个unchecked异常。也就是item57里面说的“先检查,再调用”。
Item 60: Favor the use of standard exceptions
为了更好的代码复用和通用性,我们应该尽量使用Java platform libraries提供的异常。下面介绍一些最常用的Java平台类库提供的unchecked exception。IllegalArgumentException不用说了。IllegalStateException表示对象的状态有问题,比如还未完全初始化。ConcurrentModificationException和UnsupportedOperationException也都不用说了。另外,选择哪个异常并不需要那么的精确,比如在方法验证参数的时候也可以抛出NullPointerException,如果你要求参数不能为空的话。
Item 61: Throw exceptions appropriate to the abstraction
higher layers应该catch住lower-level exceptions,并抛出exceptions that can be explained in terms of the higher-level abstraction。否则的话,client可能会收到一条莫名的异常,而且这些lower-level exceptions属于你的内部实现,不应该propagate出去。比如:
/**
* Returns the element at the specified position in this list.
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()}).
*/
public E get(int index) {
ListIterator<E> i = listIterator(index);
try {
return i.next();
} catch(NoSuchElementException e) {
throw new IndexOutOfBoundsException("Index: " + index);
}
}
这里由于上面的specification的规定,所以把NoSuchElementException转换成了IndexOutOfBoundsException,我们把这种技巧叫做exception translation,如果底层异常对于client来说是make sense的,那么就可以不用translate。
有时候为了调试的目的,可以把lower-level exception(the cause)pass给higher-level exception:
// Exception Chaining
try {
... // Use lower-level abstraction
} catch (LowerLevelException cause) {
throw new HigherLevelException(cause);
}
然后可以在HigherLevelException上用getCause()得到LowerLevelException。大多数exception都可以直接用这种constructor的形式传给他一个cause,对于有些没有这种constructor的异常,可以用Throwable.initCause方法。
但是exception translation不可滥用,最好还是能把client和这些底层调用分离,比如你可以在higher layer上默默地处理一下这些底层异常,或者在传给底层方法之前先验证一下参数。
Item 62: Document all exceptions thrown by each method
要在方法声明中throws具体的类,千万别为了省事儿,throws一个 所有可能抛出的异常的super class。所以千万不要throws Exception或者throws Throwable,这一点感觉和CLR via C#中的千万不要catch(Exception e)类似。正确做法是把每一个可能抛出的checked异常都用一个 @throws tag。
你应该尽量把可能抛出的unchecked异常也通过 @throws tag写在文档里,特别是在interface的文档里,应该说明可能抛出的unchecked异常,从而保证不同的实现的行为是一致的。
Item 63: Include failure-capture information in detail messages
这条item的意思就是:当一个程序fails due to an uncaught exception的时候,系统会自动print这个异常的stack trace,而这个stack trace包含这个异常的“toString”,通常来说这是唯一有用的信息,所以这个信息必须包括尽量多的有用的信息,以帮助查找问题根源。这个信息里面应该包括所有导致异常的参数的值,如IndexOutOfBoundsException就应该包括上界index、下界index和实际造成异常的index的值。但是并不是让你在这个信息中包含大量的文字,因为stack trace中会包括是在哪个源文件出错的,以及出错地方的行号,我们可以阅读这些源码来获取更多信息。另外,这个信息并不是给终端用户看的,所以可理解性并不是第一位的。综上,IndexOutOfBoundsException若是有这么一个constructor就好了:
/**
* Construct an IndexOutOfBoundsException.
*
* @param lowerBound the lowest legal index value.
* @param upperBound the highest legal index value plus one.
* @param index the actual index value.
*/
public IndexOutOfBoundsException(int lowerBound, int upperBound,int index) {
// Generate a detail message that captures the failure
super("Lower bound: " + lowerBound +
", Upper bound: " + upperBound +
", Index: " + index);
// Save failure information for programmatic access
this.lowerBound = lowerBound;
this.upperBound = upperBound;
this.index = index;
}
Item 64: Strive for failure atomicity
一个抛出异常的方法如果能让对象的状态还是在调用这个方法之前的状态(leave the object in the state that it was in prior to the invocation),那么这个方法就是failure atomic的,也就是可以恢复的,所以对checked exception来说应该是这样。
在immutable的对象上调用方法都是failure atomic的,就算失败,也只不过可能是阻止了一个新对象的创建,但不可能会破坏这个immutable对象的状态。
对于mutable的对象来说,最好的办法就是在执行真正的操作之前,先检查参数或者状态是否valid,比如Stack中的pop方法:
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
如果把上面代码中的“检查”去掉,那么如果是个empty stack的话,虽然也会抛出异常,但是size已经变成负数了,这时候状态就被污染了。
类似的,你也可以通过先做一些不会改变对象状态的操作(或者说计算),前提是“如果这时候没报错,那么后面的 改变对象状态的操作 就肯定不会报错”,然后再做一些改变对象状态的操作。比如向TreeSet中插入一个元素的方法,会先在这个TreeSet中搜索这个元素,如果搜索没报错,再插入。
还有一种比较少用的方法在捕获到异常后,写一些“recovery code”,从而roll back到之前的状态。
还有一种方法是,先拷贝一份这个对象,然后在这份临时的拷贝上进行修改操作,如果成功了,再copy回去。
但是当多个线程对同一个对象concurrently地进行修改操作的话,这个对象很可能会被搞成inconsistent state,比如在出现ConcurrentModificationException之后,就不应该认为这个对象还能被用了(unrecoverable)。
Item 65: Don’t ignore exceptions
这条item就是告诉我们,别做这种事儿:
// Empty catch block ignores exception - Highly suspect!
try {
...
} catch (SomeException e) {
}
你至少也应该加一条注释,说明为什么可以忽略这个异常。
我记得在CLR via C#中这种做法被叫做silence或者swallow一个异常。
《Effective Java》读书笔记 - 9.异常的更多相关文章
- Effective Java 读书笔记之八 异常
一.只针对异常的情况才使用异常 1.类具有状态相关的方法时,可采用状态测试方法和可识别的返回值两个策略. 二.对可恢复的情况使用受检异常,对编程错误使用运行时异常 1.期望调用者能够适当恢复的情况,应 ...
- Effective java读书笔记
2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己 计在16年要看12本书,主要涉及java基础.Spring研究.java并发.J ...
- Effective Java读书笔记完结啦
Effective Java是一本经典的书, 很实用的Java进阶读物, 提供了各个方面的best practices. 最近终于做完了Effective Java的读书笔记, 发布出来与大家共享. ...
- Effective Java 读书笔记(一):使用静态工厂方法代替构造器
这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文&l ...
- Effective Java 读书笔记之一 创建和销毁对象
一.考虑用静态工厂方法代替构造器 这里的静态工厂方法是指类中使用public static 修饰的方法,和设计模式的工厂方法模式没有任何关系.相对于使用共有的构造器来创建对象,静态工厂方法有几大优势: ...
- Effective Java读书笔记——第三章 对于全部对象都通用的方法
第8条:覆盖equals时请遵守通用的约定 设计Object类的目的就是用来覆盖的,它全部的非final方法都是用来被覆盖的(equals.hashcode.clone.finalize)都有通用约定 ...
- Effective Java 读书笔记(五):Lambda和Stream
1 Lamdba优于匿名内部类 (1)DEMO1 匿名内部类:过时 Collections.sort(words, new Comparator<String>() { public in ...
- Effective Java 读书笔记(四):泛型
1 不要使用原始类型 (1)术语 术语 例子 参数化类型(Parameterized type) List<String> 实际类型参数(Actual type parameter) St ...
- Effective Java读书笔记--对所有对象都通用的方法
1.覆盖equals请遵守通用规定.不需要覆写equals的场景:a.类的每个实例都是唯一的.b.类不需要提供"逻辑相等"的测试功能.c.超类已经覆盖了equals的方法.d.类是 ...
- Effective Java 读书笔记之十 序列化
一.谨慎地实现Serializable接口 1.一旦一个类被发布,就大大地降低了“改变这个类的实现”的灵活性. 2.仔细设计类的序列化形式而不是接受类的默认虚拟化形式. 3.反序列化机制是一个“隐藏的 ...
随机推荐
- UDP协议&socketserver模块
UDP协议&socketserver模块 一.UDP协议 1.1 UDP实现简单通信 服务器 ------------------------------------------------- ...
- 原生js:click和onclick本质的区别(转https://www.cnblogs.com/web1/p/6555662.html)
原生javascript的click在w3c里边的阐述是DOM button对象,也是html DOM click() 方法,可模拟在按钮上的一次鼠标单击. button 对象代表 HTML 文档中的 ...
- HNUST-1148 ACM ranking rules(简单模拟)
1148: ACM ranking rules 时间限制: 1 Sec 内存限制: 128 MB提交: 16 解决: 12[提交][状态][讨论版] 题目描述 ACM contests, like ...
- MySQL中的索引简介
MySQL中的SQL的常见优化策略 MySQL中的索引优化 MySQL中的索引简介 一. 索引的优点 为什么要创建索引?这是因为,创建索引可以大大提高系统的查询性能. 第一.通过创建唯一性索引,可以保 ...
- C++ new、delete、namespace关键字。
C++ 中的动态内存分配: C++与C语言分配内存关键字不同,C语言中的动态内存分配是通过 malloc(分配内存) 与 free(释放内存)完成.C++使用new(分配内存) delete(释放内 ...
- Delphi 注释
- websocket之拨云见雾
websocket是基于http相应的特性弥补其不足(就是个socket,不再是一次请求一次相应) 但缺点就是只有在版本较高的浏览器才支持websocket. 浏览器: <script type ...
- 安装与学习laravel
安装 composer cd /var/www/html curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/ ...
- eddx
eddx是亿图绘图文件,可以使用EdrawSoft Edraw Max软件打开.这是一款流程图绘图软件,它内置丰富的预定义模板和例子,可以创建各种图示.包括商务绘图.工程及科学绘图.思维导图和数据库. ...
- SQL代码
SELECT SCHEMA_NAME(SCHEMA_ID)AS ID,name as Table_name FROM sys.tables;--查询表视图 查询表视图