EffectiveJava笔记

EffectiveJava第9章-异常

2017-06-10  本文已影响0人  wangcanfeng

第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块会使异常达不到应有的目的。

上一篇下一篇

猜你喜欢

热点阅读