EffectiveJava第9章-异常

第57条:只针对异常的情况处理异常

try{
    int i = 0;
    while(true)
        range[i++].climb(); 
}catch(ArrayIndexOutOfBoundsException e){
}

用抛出(throw)、捕获(catch)、忽略ArrayIndexOutOfBoundsException的手段来达到终止无限循环的目的。这是极其不合适的。原因有:

1.异常机制的设计初衷是用于不正常的情形,所以很少会有JVM实现试图对它们进行优化,使得与显式的测试一样快速。
2.把代码放在try-catch块中反而阻止了现代JVM实现本来可能要执行的某些特定优化。
3.对数组进行遍历的标准模式并不会导致冗余的检查。有些现代的JVM实现会将它们优化掉。

基于异常的循环模式不仅模糊了代码的意图,降低了它的性能,而且它还不能保证正常的工作!可能掩盖Bug(捕捉到了异常,但不做任何处理)!

这条原则对于API设计也有启发。设计良好的API不应该强迫它的客户端为了正常的控制流而使用异常。

1.如果类具有“状态相关”的方法,往往也应该有“状态测试”的方法。比如,Iterator接口有一个“状态相关”的next方法,和相应的状态测试方法hasNext。所以对集合进行迭代的标准模式:

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

而不是通过异常捕获的方式结束遍历。

2.如果状态相关的方法被调用时,该对象处于不适当的状态之中,它就会返回一个可识别的值,比如null。但这个并不适合Iterator,因为null也是next的合法返回值。

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

Java程序设计提供三种可抛出的异常,受检异常、运行时异常和错误。(运行时异常和错误又称为非受检异常)。

受检异常应该被捕获,非受检异常不应该被捕获。

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

受检异常强迫程序员处理异常的条件,大大增强了可靠性。过分使用受检的异常会使API使用起来非常不方便。

如果正确地使用API并不能阻止这种异常条件的产生,
并且一旦产生异常,使用API的程序员可以立即采取有用的动作(恢复),
除非上述两个条件都成立,否则更适合使用未受检的异常。

把受检异常变为未受检异常:
把抛出异常的方法分成两个方法,其中第一个方法返回一个boolean,表明是否应该抛出异常。

try{
  obj.action(args);
}catch(TheCheckedException e){
  //Handle exceptional condition
  ...
}
重构为
if(obj.actionPermitted(args)){
  obj.action(args);
}else{
  //Handle exception condition
  ...
}

如果明知道会调用成功,或者不介意由于调用失败而导致的线程终止,可以使用:
 obj.action(args);

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

常见的异常

异常 使用场合
NullPointerException 在禁止使用null的情况下,对象为null
IndexOutOfBoundsException 下表参数值越界
IllegalArgumentException 非null的参数值不正确
IllegalStateException 对于方法调用而言,对象状态不合适

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

当方法传递 由低层抽象抛出的异常时,往往会出现方法抛出的异常与它所执行的任务没有明显的联系。

因此,高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常。这叫“异常转译”

try{
    //Use lower-level abstraction to do our bidding
    ...
}catch(LowerLevelException e){
    throw new HigherLevelException(...);
}

例如AbstractSequentialList类:

    /**
     * Returns the element at the specified position in this list.
     *
     * <p>This implementation first gets a list iterator pointing to the
     * indexed element (with <tt>listIterator(index)</tt>).  Then, it gets
     * the element using <tt>ListIterator.next</tt> and returns it.
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        try {
            return listIterator(index).next();
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }

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

描述一个方法所抛出的异常,是正确使用这个方法时所需要文档的重要组成部分。

始终要单独地声明受检的异常,并且利用Javadoc的@throws标记,准确地记录下抛出每个异常的条件。明确的声明所有抛出的异常。

对于可能抛出的非受检异常,也要为它们建立文档,这样可以有效地描述出这个方法被成功执行的前提条件

//ConcurrentHashMap的get方法,key不能为空值。
     /**
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     * ......
     *
     * @throws NullPointerException if the specified key is null
     */
    public V get(Object key) {
        ......
    }

在接口中的方法,在文档中记录下它可能抛出的未受检异常显得尤为重要。这份文档成了该接口的通用约定。比如ConcurrentMap接口的putIfAbsent()方法可能抛出NullPointerException,文档中约定了任何继承了ConcurrentMap的类的key和value都不能为null。

public interface ConcurrentMap<K, V> extends Map<K, V> {
   /**
    * If the specified key is not already associated
    * with a value, associate it with the given value.
    * This is equivalent to
    * <pre>
    *   if (!map.containsKey(key))
    *       return map.put(key, value);
    *   else
    *       return map.get(key);</pre>
    * except that the action is performed atomically.
    *
    * @param key key with which the specified value is to be associated
    * @param value value to be associated with the specified key
    * @return the previous value associated with the specified key, or
    *         <tt>null</tt> if there was no mapping for the key.
    *         (A <tt>null</tt> return can also indicate that the map
    *         previously associated <tt>null</tt> with the key,
    *         if the implementation supports null values.)
    * @throws UnsupportedOperationException if the <tt>put</tt> operation
    *         is not supported by this map
    * @throws ClassCastException if the class of the specified key or value
    *         prevents it from being stored in this map
    * @throws NullPointerException if the specified key or value is null,
    *         and this map does not permit null keys or values
    * @throws IllegalArgumentException if some property of the specified key
    *         or value prevents it from being stored in this map
    *
    */
   V putIfAbsent(K key, V value);
   
   ......
}

使用Javadoc的@throws标签记录下一个方法可能抛出的每个未受检异常,但是不要使用throws关键字将未受检的异常包含在方法的声明中。
throws关键字只将受检的异常包含在方法的声明中。这样有Javadoc的@throws标签所产生的文档就会有明显的提示信息来帮助区分受检异常和非受检异常。

总结:使用Javadoc的@throws标签将方法可能抛出的所有异常都要明确地记录下来,而在方法声明的throws关键字,只记录受检异常。这样程序员就可以明确地区分方法所抛出来的受检异常和非受检异常。

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

为了捕获失败,异常的细节信息应该包含所有“对异常有贡献”的参数和域的值。

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

一般而言,失败的方法调用应该使对象保持在被调用之前的状态。具有这种属性的方法被称为具有失败原子性。

有几种途径可以实现这种效果:

1.设计一个不可变对象;
2.在执行操作之前检查参数的有效性;

public Object pop(){
    if(size == 0)
        throw new EmptyStackException();
    Object result = elememts[--size];
    elements[size] = null; //销除废弃的引用
    return result;
}

如果不对size进行检查,这个方法仍会抛出异常,但是,size的值保持在不一致的状态(-1)之中。

3.写一段恢复代码,回滚
4.在对象的一份临时拷贝上执行操作,当操作完成之后再用临时拷贝中的结果代替对象的内容。比如,Collections.sort在执行排序之前,首先把输入列表转到了一个数组中。

第65条:不要忽略异常

try{
  ...
}catch(SomeException e){
}

空的catch块会使异常达不到应有的目的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,302评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,232评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,337评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,977评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,920评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,194评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,638评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,319评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,455评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,379评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,426评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,106评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,696评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,786评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,996评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,467评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,043评论 2 341

推荐阅读更多精彩内容