首页 > 学院 > 开发设计 > 正文

[Effective Java]第九章 异常

2019-11-14 22:45:34
字体:
来源:转载
供稿:网友
[Effective java]第九章 异常声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.VEVb.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.VEVb.com/jiangzhengjun/p/4255688.html 第九章 异常 57、 只针对异常的情况才使用异常

也许你在将来会碰到下面这样的代码,它是基本异常模式的循环:

try{

int i = 0;

while(true)

range[i++].climb();

}catch(ArrayIndexOutOfBoundsException e){

}

这所以有些人会这么做,是因为他们企图利用Java的错误判断机制来提高性能,因为VM对每次数组访问都要检查越界情况,即使是使用for-each,他们认为正常的循环终止测试只是被编译器隐藏了,但在循环中仍然可见,这种考虑无疑是多余的。

实例上,在现代的JVM实例上,基本异常的模式比标准模式要慢得多。

异常应该只用于异常的情况下,它们永远不应该用于正常的控制流。

设计良好的API不应该强迫它的客户端为了正常的控制流程而使用异常。如Iterator接口有一个“状态相关”的next方法,和相应的状态测试方法hasNext,这使得利用传统的for循环对集合进行迭代的标准模式成为可能:

for(Iterator<Foo> i = coolection.iterator(); i.hasNext()){

Foo foo = i.next();

}

如果Iterator缺少hasNext方法,客户端将被迫改用下面的做法:

try{

Iterator<Foo> i = collection.iterator();

while(true){

Foo foo = i.next();

}

}catch(NoSuchElementException e){

}

另一种提供单独的状态测试方法的做法是,如果“状态相关的”方法被调用时,该对象处于不适当的状态之中时,它就会返回一个可识别的值,比如null。这种方法对于Iterator而言不合适,因为null是next就去的合法返回值。

对于“状态测试方法”和“可识别的返回值”这两种做法,有些告诫:如果对象在缺少外部同步的情况下被并发访问,或者可被外界改变状态,使用可被识别的返回值可能是很有必要的,因为在调用“状态测试”方法和调用对应的“状态相关”方法的时间间隔之中,对象的状态有可能会发生变化。但从性能角度考虑,使用可被识别的返回值要好些。如果所有其他方面都是等同的,那么“状态测试”方法则略优先可被识别的返回值。

总之,异常是为了在异常情况下使用而设计的,不要将它们用于普通的控制流,也不要缩写迫使它们这么做的API。

>>>《PRactice Java》异常拾遗<<<

如果finally块中出现了异常没有捕获或者是捕获后重新抛出,则会覆盖掉try或catch里抛出的异常,最终抛出的异常是finally块中产生的异常,而不是try或catch块里的异常,最后会丢失最原始的异常。

如果在try、catch、finally块中都抛出了异常,只是只有一个异常可被传播到外界。记住,最后被抛出的异常是唯一被调用端接受到的异常,其他异常都被掩盖而后丢失掉了。如果调用端需要知道造成失几的初始原因,程序之中就绝不能掩盖任何异常。

请不要在try块中发出对return、break或continue的调用,万一无法避免,一定要确保finally的存在不会改变函数的返回值(比如说抛异常啊、return啊以及其他任何引起程序退出的调用)。因为那样会引起流程混乱或返回值不确定,如果有返回值最好在try与finally外返回。

不要将try/catch放在循环内,那样会减慢代码的执行速度。

如果构造器调用的代码需要抛出异常,就不要在构造器处理它,而是直接在构造器声明上throws出来,这样更简洁与安全。因为如果在构造器里处理异常或将产生异常的代码放在构造器之外调用,都将会需要调用额外的方法来判断构造的对象是否有效,这样可能忘记调用这些额外的检查而不安全。

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

Java程序设计语言提供了三种异常:受检的异常(checked exception)、运行时异常(run-time exception)和错误(error)。关于什么时候适合使用哪种异常,虽然没有明确的规定,但还是有些一般性的原则的。

检测性异常通常是由外部条件不满足而引起的,只要条件满足,程序是可以正常运行的,即可在不修改程序的前提下就可正常运行;而运行时异常则是由于系统内部或编程时人为的疏忽而引起的,这种异常一定要修正错误代码后再能正确运行。受检异常对客户是有用的,而运行时异常则是让开发人员来调试的,对客户没有多大的用处。

在决定使用受检异常还是未受检异常时,主要的原则是:如果期望调用者能够适当地恢复,对于这种情况就应该使用受检的异常。抛出的受检异常都是对API用户的一种潜在的指示:与异常相关的条件是调用这个方法的一种可能的结果。

有两种未受检的异常:运行时异常和错误。在行为上两种是等同:它们都不需要捕获。如果抛出的是未受检异常或错误,往往就属于不可恢复的情形,继续执行下去有害无益。如果程序未捕获这样的异常或错误,将会导致线程停止,并出现适当的错误消息。

用运行时异常来表明编程错误。大多数的运行时异常都表示违返了API规约,API的客户同有遵守API规范。例如,数组访问的约定指明了数组的下标值必须在零和数组长度减1之间,ArrayIndexOutOfBoundsException表明了这个规定。

按照惯例,错误往往被JVM保留用于表示资源不足、约束失败,或者其他程序无法继续执行的条件。由于这已经是个几乎被普遍接受的惯例,因此最好不要再实现任何新的Error子类。因此,你实现的所有未受检异常都应该是RuntimeException的子类或间接是的。

总而言这,对于可恢复的情况,使用受检的异常;对于程序错误,则使用运行时异常。当然,这也不总是这么分明的。例如,考虑资源枯竭的情形,这可能是由于程序错误而引起的,比如分配了一块不合理的过大的数组,也可能确实是由于资源不足而引起的。如果资源枯竭是由于临时的短缺,或是临时需求太大所造成的,这种情况可能就是可恢复的。API设计者需要判断这样的资源枯竭是否允许。如果你相信可允许恢复,就使用受检异常,否则使用运行时异常。如果不清楚,最好使用未受检异常(原因请见第59条)。

API的设计都往往会忘记,异常也是个完全意义上的对象,可以在它上面定义任意的方法,这些方法的主要用途是为捕获异常的代码而提供额外的信息,特别是关于引发这个异常条件的信息。如果没有这样的方法,API用户必须要懂得如何解析“该异常的字符串表示法”,以便获得这些额外的信息,这是极不好的作法。类很少以文档的形式指定它们的字符串表示的细节,因此,不同的实现,不同的版本,字符串表示可能会大相径庭,因此,“解析异常的字符串表示法”的代码可能是不可移植的,也是非常脆弱的。

因为受检异常往往指明了可恢复的条件,所以,这于这样的异常,提供一些辅助方法尤其重要,通过这些方法,调用都可以获得一些有助于恢复的信息。例如,假设因为没有足够的钱,他企图在一个收费电话上呼叫就会失败,于是抛出检查异常。这个异常应该提供一个访问方法,以便用户所缺的引用金额,从而可以将这个数组传递给电话用户。

59、 避免不必要地使用受检异常

受检异常与运行时异常不一样,它们强迫程序员处理异常的条件,大大增强了可靠性,但过分使用受检异常会使用API使用起来非常不方便。如果方法抛出一个或者多个受检异常,调用都就必须在一个或多个catch块中处理,或者将它们抛出并传播出去。无论是哪种,都会给程序员添加不可忽视的负担。当然,如果程序员能处理这一类异常,则不算做负担,但以下决对是负担:

}catch(TheCheckedException e){

throw new AssertionError();//断言不会发生异常

}

}catch(TheCheckedException e){//哦,失败了,不能恢复

e.printStackTrace();

System.exit(1);

}

上面两种方式都不是最好的处理方式,之所以程序员这样处理,是因为他们认为这根本就是一种不可恢复的异常(当然程序员这种认为要是正当的),如果使用API的程序员无法做得比这更好,那么未受检异常可能更为合适,所以异常设计者应将TheCheckedException设计成运行时异常会更好些。这种例子的反例就是CloneNotSupportedException,它是Object.clone抛出来的,而Object.clone应该只是在实现了Cloneable的对象上者可以被调用,这显然是API调用都未实现该接口所导致,除非程序实现该接口,否是不可恢复的,所以CloneNotSupportedException应该设计成运行时异常或许更加合理一些。

如果方法只抛出单个受检异常,也会导致该方法不得在try块中,在这种情况下,应该问自己,是否有别的途径来避免API调用者使用受检的异常。这里提供这样的参考,我们可以把抛出的单个异常的方法分成两个方法,其中一个方法返回一个boolean,表明是否该抛出异常。这种API重构,把下面的调用:

try{//调用时检查异常

obj.action(args);//调用检查异常方法

}catch(TheCheckedExcption e){

//处理异常条件

...

}

重构为:

if(obj.actionPermitted(args)){//使用状态测试方法消除catch

obj.action(args);

}else{

//处理异常条件

...

}

这种重构并不总是合适的,但在合适的地方,它会使用API用起来更加舒服。虽然没有前者漂亮,但更加灵活——如果程序员知道调用肯定会成功,或不介意由调用失败而导致的线程终止,则下面为理为简单的调用形式:

obj.action(args);

如果你怀疑这个简单的调用是否成功,那么这个API重构则可能就是恰当的。这种重构之后的API在本质上等同于第57条件的“状态测试方法”,并且,同样的告诫也要遵循(告诫参考57)。

60、 优先使用标准异常

java平台提供了一组基本未受检查的异常,它们满足了绝大多数API的异常抛出的需要。

重用现有的异常好处最主要的是,易学和易使用,与已经熟悉的习惯用法一致。第二可读性更好,不会出现不熟悉的异常。

最经常被重用的异常是IllegalArgumentException。当调用者传递的参数值不合适的时候,往往就会抛出这个异常。如需要一个正数而传递的是一个负数时。

另一个经常被重用的异常是IllegalStateException。如果因为对象的状态而使用调用非法,则会抛出。如某个对象被正确初始化之前就调用。

Word-spacing: 0px; text-transform: none; word-break: normal; margin: 0cm 0cm 0pt; l

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表