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.异常的更多相关文章

  1. Effective Java 读书笔记之八 异常

    一.只针对异常的情况才使用异常 1.类具有状态相关的方法时,可采用状态测试方法和可识别的返回值两个策略. 二.对可恢复的情况使用受检异常,对编程错误使用运行时异常 1.期望调用者能够适当恢复的情况,应 ...

  2. Effective java读书笔记

    2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己 计在16年要看12本书,主要涉及java基础.Spring研究.java并发.J ...

  3. Effective Java读书笔记完结啦

    Effective Java是一本经典的书, 很实用的Java进阶读物, 提供了各个方面的best practices. 最近终于做完了Effective Java的读书笔记, 发布出来与大家共享. ...

  4. Effective Java 读书笔记(一):使用静态工厂方法代替构造器

    这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文&l ...

  5. Effective Java 读书笔记之一 创建和销毁对象

    一.考虑用静态工厂方法代替构造器 这里的静态工厂方法是指类中使用public static 修饰的方法,和设计模式的工厂方法模式没有任何关系.相对于使用共有的构造器来创建对象,静态工厂方法有几大优势: ...

  6. Effective Java读书笔记——第三章 对于全部对象都通用的方法

    第8条:覆盖equals时请遵守通用的约定 设计Object类的目的就是用来覆盖的,它全部的非final方法都是用来被覆盖的(equals.hashcode.clone.finalize)都有通用约定 ...

  7. Effective Java 读书笔记(五):Lambda和Stream

    1 Lamdba优于匿名内部类 (1)DEMO1 匿名内部类:过时 Collections.sort(words, new Comparator<String>() { public in ...

  8. Effective Java 读书笔记(四):泛型

    1 不要使用原始类型 (1)术语 术语 例子 参数化类型(Parameterized type) List<String> 实际类型参数(Actual type parameter) St ...

  9. Effective Java读书笔记--对所有对象都通用的方法

    1.覆盖equals请遵守通用规定.不需要覆写equals的场景:a.类的每个实例都是唯一的.b.类不需要提供"逻辑相等"的测试功能.c.超类已经覆盖了equals的方法.d.类是 ...

  10. Effective Java 读书笔记之十 序列化

    一.谨慎地实现Serializable接口 1.一旦一个类被发布,就大大地降低了“改变这个类的实现”的灵活性. 2.仔细设计类的序列化形式而不是接受类的默认虚拟化形式. 3.反序列化机制是一个“隐藏的 ...

随机推荐

  1. python3 修改大数据量excel内容

    最好使用python3 64位 对excel的修改操作: from openpyxl import load_workbook import time #打开一个excel表格.xlsx wb = l ...

  2. 如何跳出iframe父级,打开一个链接

    假设使用window的跳转方法 ①window.parent.frames.location.href = "1.html";    //可以跳出iframe父级      此方法 ...

  3. VeryNginx故障排除

    在安装和使用 VeryNginx 的过程中可能会遇到一些问题,下面列举了常见的问题及对应的解决方案,供参考. Q: run "python instal.py install all&quo ...

  4. 依据系统语言、设备、url 重定向对应页面

    1. 思路 获取浏览器语言.页面名称.区分手机端与电脑 根据特定方式命名 html 文件,然后独立文件,重定向 eg: - root -  gap.html     gap -    index.ht ...

  5. java面试题全集(下)

      这部分主要是开源Java EE框架方面的内容,包括Hibernate.MyBatis.Spring.Spring MVC等,由于Struts 2已经是明日黄花,在这里就不讨论Struts 2的面试 ...

  6. ifconfig - 配置网络接口

    总览 ifconfig [接口] ifconfig 接口 [aftype] options | address ... 描述 ifconfig 用于配置常驻内核的网络接口.它用于在引导成功时设定网络接 ...

  7. 安卓的几种alert对话框

    @Override public void onClick(View v) { switch (v.getId()) { case R.id.d1: AlertDialog.Builder build ...

  8. phpstudy使用PHP+nginx配置Laravel

    一.需要注意把vhosts.conf文件内root项目路径的\换成/例如 root "D:/laravelApp/test/public"; 二.若文件根目录下没有 .env1.. ...

  9. Java中通过相对路径来定位文件

    通常我们定位文件都是通过绝对路径进行定位,比如“F:/Java/bin/test/test.java”,这样的缺点就是,一旦项目文件移动,这些路径就完全失效. 所以,下面我们来介绍一种通过相对路径来定 ...

  10. MapReduce计数程序(自主复习)

    1.MyWordCount类 注意: 1.本机+测试,两个注释都放开 2.本机跑集群,要开异构平台为true 3.集群跑,把两个注释都注起来,然后在集群上面跑 package com.littlepa ...