【08】Effective Java - 异常

一、只针对异常的状况才使用异常

(1)基于异常的模式比标准模式要慢不少
try{
   int i = 0;
   while(true)
     range[i++].climb();
}catch(ArrayIndexOutOfBoundsException e){
}

  企图经过异常来终止循环,是对异常的误用,异常是针对不正常状况才使用的。上面的基于异常模式运行上,比正常的模式在性能上慢不少,2倍或以上。java


(2)不要将异经常使用于正常控制流

对于API的设计也是如此,不要强迫它的客户端为了正常的控制流而使用异常程序员

通常的处理方法有两种:数组

A、使用状态监测

    若是对象的状态是线程安全的话,推荐使用,例如安全

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

  这里的hasNext就是状态监测并发

  若是使用异常的话,客户端就得这样性能

try{
   Iterator<Foo> i = collection.iterator();
   while(true){
      Foo foo = i.next();
   }
}catch(NoSuchElementException e){
}

 

B、返回可识别的值

    对于iterator这种状况,返回null是可行的。从性能角度看,返回可识别的值这种方法较好。学习


二、对可恢复的状况使用受检异常,程序错误使用运行时异常

(1)受检异常(checked exception)

     若是指望调用者可以适当的恢复,对于这种状况,应该使用受检异常。this

     API设计者让API用户面对受检异常,以强制用户从这个异常条件中恢复过来,用户能够忽视这样的强制要求,只需捕获异常并忽略便可,但这每每不是一个好方法。spa


(2)运行时异常(runtime exception)

    一般用来代表程序错误,好比precondition violation,前提违例,即API的客户没有遵循API规范创建的约定线程


三、避免没必要要的使用受检的异常

    受检异常本质上没有给程序员提供任何好处,反而须要付出努力,使得程序变得复杂。

    把受检异常变为非受检异常的一个方法,就是把抛出异常的方法拆分为两个,第一个方法返回boolean,代表是否应该抛出异常。例如iterator的hasNext,这种重构并不老是恰当的,可是若是用的合理,API使用起来就更加舒服。固然这种等同于状态监测的方法,若是线程不安全的话,不推荐这种重构。


四、优先使用标准的异常

(1)重用现有异常的好处

 A、使得你的API更加易于学习

 B、可读性更好一些

 C、异常类越少,则内存印迹越小,装载这些类的开销也就越少


(2)常见的通用异常

A、IllegalArgumentException

     传递参数不合适的时候

B、IllegalStateException

    由于接受对象的状态而使得调用非法,好比在未初始化完毕以前使用对象

C、ConcurrentModificationException

    并发修改

D、UnsupportedOperationException

    好比对只支持追加操做的list进行删除操做


五、抛出与抽象相对应的异常

(1)异常转义

    若是方法抛出的异常与它所执行的任务没有明显的联系,这种情形将会使得人们不知所措,特别是当方法传递由低层抽象抛出的异常时,这除了让人困惑以外,也让实现细节污染了更高层的API。

    为了不这个问题,须要进行异常转义(exception transaction),即更高层的实现应该捕获低层的异常,同时抛出能够按照高层抽象进行解释的异常。好比List<E>中的get方法,对低层AbstractSequentialList类抛出的异常进行转义。

public E get(int index){
   ListIterator<E> i = listIterator(index);
   try{
      return i.next();
   }catch(NoSuchElementException e){
      throw new IndexOutOfBoundsException("Index:"+index);
   }
}


(2)异常链(exception chaining)

      异常链是异常转义的特殊形式,若是低层的异常对于调试致使高层异常的问题很是有帮助的话,使用异常链就很合适。大多数标准的异常都有支持链的构造器(Throwable cause),对于没有支持链的异常,能够利用Throwable的initCause方法设置缘由。

     尽管异常转译与不加选择地从低层传递异常的作法有所改进,可是它也不能被滥用。处理来自低层的异常的最好的作法就是,在调用低层方法以前,先把来自高层的参数校验成功,从而尽量避免在低层抛出异常。


六、每一个方法抛出的异常都要有文档

(1)利用@throws标记抛出的受检异常

       同时记录抛出该异常的条件

(2)不要泛泛地抛出Throwable,Exception

       这样没有给调用者任何准确的指导信息


七、在细节消息中包含能捕获失败的信息

(1)异常的字符串表示法

     即异常的toString方法,通常包含异常的类名以及异常细节

(2)异常细节应该包含全部“对该异常有贡献”的参数和域的值

     好比IndexOutOfBoundsException的异常细节应该包含下界、上界以及没有落在界内的下标值。

     能够定义一个构造器,抛出异常时,传入这些必要的参数,而后自动产生细节信息。

public IndexOutOfBoundsException(int lowerBound,int upperBound,int index){
    super("lower bound:"+lowerBound+
    ",upper bound:"+upperBound+
    ",index:"+index);
    this.lowerBound = lowerBound;
    this.upperBound = upperBound;
    this.index = index;
}


(3)异常细节不该该与用户层次的错误信息混为一谈

    异常细节通常是让程序员去分析失败缘由的


八、努力使失败保持原子性

(1)失败原子性

   即失败的方法调用应该使对象保持在被调用以前的状态。


(2)实现失败原子性的方法

A、设计一个不可变对象

B、对于可变对象,在执行操做以前检查参数有效性

    扩展:尽量调整计算过程的顺序,使得可能失败的部分发生在对象被修改以前

C、编写一段恢复代码(recovery code)

     不是很经常使用呢,能够经过拦截处理

D、拷贝对象进行操做,成功以后用其替代对象的内容

     好比Collections.sort在执行排序以前,先把输入列表转到一个数组中,以便下降在排序的内循环中访问元素所须要的开销,另一个考虑就是当排序失败的时候,也能保证输入列表保持原样。

总之,做为方法规范的一部分,产生的任何异常都应该让对象保持在该方法调用以前的状态。


九、不要忽略异常

(1)空的catch块会使异常达不到应有的目的

(2)不重复地记录异常,能够在必要的时候调查异常缘由

相关文章
相关标签/搜索