《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.反序列化机制是一个“隐藏的 ...
随机推荐
- 从头到尾说一次 Java 垃圾回收,写得非常好! (转)
之前上学的时候有这个一个梗,说在食堂里吃饭,吃完把餐盘端走清理的,是 C++ 程序员,吃完直接就走的,是 Java 程序员.
- PropertyUtilsBean 将bean转成map
public static Map<String,String> beanToMap(Object bean) { Map<String,String> params =Map ...
- vue项目1-pizza点餐系统2-配置路由跳转
功能目标:点击导航栏中的菜单.主页.路由跳转到不同的组件,点击谁就在在导航栏下展示谁. 1.在router文件夹中(在用脚手架cli搭建项目时,有个couter的选yes)的index.js中,导入如 ...
- 现身说法:面对DDoS攻击时该如何防御?
上周,我们的网站遭到了一次DDoS攻击.虽然我对DDoS的防御还是比较了解,但是真正遇到时依然打了我个措手不及.DDoS防御是一件比较繁琐的事,面对各种不同类型的攻击,防御方式也不尽相同.对于攻击来的 ...
- js 判断确切判断Array和Object
js的数组其实是特殊的对象. 这就导致: typeof [1,2,3] === 'object' [1,2,3] instanceof Object 和 [1,2,3] instanceof Arr ...
- 008-saltstack之salt-ssh
根据以往运维工作中操作经验来说,当管理上百台上千台服务器时,选择一款批量操作工具是及其有必要的.早期习惯于在ssh信任关系的前提下做for;do;done循环语句的批量操作,后来逐渐趋于使用批量工具操 ...
- Big Data(六)用户权限实操&HDFS-API实操
创建用户实操 1.创建用户god useradd god passwd god 2.设置ssh免密 ssh-copy-id -i id_dsa node02 3.修改hdfs-site.xml中的ss ...
- 18Bootstrap
1 概念 一个前端开发的框架,Bootstrap,来自 Twitter,是目前很受欢迎的前端框架.Bootstrap 是基于 HTML.CSS.JavaScript 的,它简洁灵活,使得 Web 开发 ...
- jenkins项目名称改后,同步nginx配置
jenkins项目名称修改后,workspace的名称会同步更改,构建完了和原来的不是一个路径,如果每个前端项目一个单独的tomcat的话,需要更改nginx配置 /etc/nginx/conf.d
- Spring、SpringMVC注解方式整合
1 原理 Web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer文件. 加载META-IN ...