第39条:只针对不正常的条件才使用异常
try{
int i=0;
while(true)
a[i++].f();
}catch(ArrayIndexOutOfBoundsException e){
}
三个错误:
1.异常机制的设计初衷是用于不正常的情形,所以很少会有jvm实现试图对它们的性能做优化。所以,创建,抛出和捕获异常的开销是昂贵的。
。2.把代码放在try-catch块中反而阻止了现代jvm实现本来可能执行的某些特定的优化。
3.对数组进行遍历的标准模式并不会导致冗余的检查,有些现代的jvm实现会将它们优化掉。
基于异常的循环不仅模糊了代码的意图,降低了它的性能,而且它不能保证正常工作。
异常只应该被用于不正常的条件,它们永远不应该被用于正常的控制流。更一般的,你应该优先使用标准的,容易理解的模式,而不是那些声称可以提供更好性能的,弄巧成拙的模式。即使真的能够改进性能,面对jvm实现的不断改进,这种模式的性能优势也许不复存在。然而,由这种过渡聪明的模式带来的隐藏的错误,以及维护的痛苦却依然存在。
一个设计良好的api不应该强迫它的客户为了正常的控制流而使用异常。如果一个类具有一个状态相关的方法,即只有特定的不可预知的条件下才可以被调用的方法,那么这个类往往也应该有一个单独的状态测试方法,即只是是否可以调用第一个方法。例如,Iterator类有一个状态相关的next方法,它返回迭代过程中的下一个元素,对应的状态测试方法为hasNext。这使得使用标准模式称为可能。
第40条:对于可恢复的条件使用被检查的异常,对于程序错误使用运行时异常。
java提供三种可抛出结构:被检查的异常,运行时异常,和错误。
主要的原则是:如果期望调用者能够恢复,那么对于这样的条件应该使用被检查的异常。
有两种未被检查的可抛出结构:运行时异常和错误。在行为上两者是等同的;他们都是不需要也不应该被捕获的抛出物。如果一个程序抛出一个未被检查的异常或者一个错误,则往往是不可恢复的情形,继续执行下去有害无益。如果一个程序没有捕获这样的可抛出结构,将会导致当前线程停止,并伴以一个适当的错误消息。
用运行时异常来指明程序错误。大多数的运行时异常都是表明前提违例。所谓前提违例是指api的客户没有遵守api规范建立的约定。
你所实现的所有的为被检查的抛出结构都应该是RuntimeException的子类(直接或间接的)、最好不要实现Error子类。
总而言之,一般对于可恢复的条件使用被检查时异常,对于程序错误,使用运行时异常。
第41条:避免不必要的使用被检查的异常。
被检查的异常是java的一个很好特性,于返回代码不同,它强迫程序员处理例外的条件,大大提高了可靠性。然而,过分使用被检查的异常会使api用起来非常不方便。如果一个方法会抛出一个或者多个被检查的异常,那么调用该方法的代码必须在一个或者多个catch块中处理异常或则声明这些异常,以便让它们传播出去,无论哪个方法都给程序员添加了不可忽视的负担。
把被检查的异常变成未被检查的异常是一种技术是,把这个要抛出异常的方法分成2个方法,其中第一个方法返回一个boolean,表明是否应该抛出异常。例如:
try{
obj.action(args);
}catch(){
}
转化为:
if(obj.actionPermitted(args)){
obj.action(args);
}else{
}
这种转化并不是总是合适的,但是合适的地方,总会让api用起来更舒服。虽然后者调用序列没有前者漂亮,但是这样得到的api更加灵活。如果一个对象将会在缺少外部同步的情况下被并发访问,或者可被外界改变状态,那么这种状态是不合适的,因为在actionPermitted和action这两个调用的时间间隔中,对象状态有可能发生变化。
第42条:尽量使用标准的异常
重用现有的异常有很多好处
1.最主要的是,它使得你的api更加易于学习和使用,因为它与程序原来已经熟悉的习惯用法一致。
2。对于用到这些api的程序而言,他们的可读性更好,因为他们不会冲刺着程序员不熟悉的异常。
3.异常类越少,意味着内存占用越小,并且装载这些类的时间开销也越小。
最后,一定要清楚,选择重用哪一个异常并不总是一门精确的科学。
第43条:抛出的异常要适合于相应的抽象
高层的实现应该铺货底层的异常,同时抛出一个可以按照高层抽象进行解释的异常。这种做法被称为异常转译。例子:
try{
}catch(LowerLevelException e){
throw new HigherLevelexception();
}
一种特殊形式的异常转译被称为异常连接,如果底层的异常对于调用该异常被抛出的情形非常有帮助,那么使用异常连接是很适合的。在这种方法中,底层的异常被高层的异常保存起来,并且高层的异常提供一个公有的访问方法来获得底层的异常:
try{
}catch(LowerLevelException e){
throw new HigherLevelexception(e);
}
尽管异常转意比不加选择的传递底层异常的做法有所改进,但是它不能被滥用。如果可能的话,处理来自底层异常的最好做法是,在调用底层方法之前确保它们会成功执行,而避免它们会抛出异常。有时候,你可以在给底层传递实参之前,显示的检查这些实参的有效性,从而避免底层方法抛出异常。
如果无法阻止来自底层的异常,那么其次做法是让高层来处理这些异常,从而将高层方法的调用者于底层的问题隔离开。
如果既不能阻止来自底层的异常,也无法将它们与高层隔离开,那么一般做法是使用异常转译。只有在底层方法的规范碰巧可以保证它抛出的异常对于高层也是合适的情况下,才可以将异常从底层传播到高层。
第44条:每个方法抛出的异常都要有文档
描述一个方法所抛出的异常,是正确使用这个方法所需要文档的重要组成部分,因此,花点时间仔细的为每个方法抛出的异常做文档是特别重要的。
总是要单独的声明被检查的异常,并且利用javadoc的@throws标记,准确的记录下每个异常被抛出的条件。如果一个方法可能会抛出多个异常类,则不要使用“快捷方式”,即声明它会抛出这些异常类的某个超类。作为一个极端的例子,永远不要声明一个方法throws exception。这样的声明不仅没有为该程序员提供关于这个方法能够抛出哪些异常的任何指导信息,而且大大的妨碍了该方法的使用,因为它实际上掩盖了在同样的执行环境中该方法可能会抛出的任何其他异常。
使用javadoc的@throws标签记录下一个方法可能会抛出的每个未被检查的异常,但是不要使用throws关键字将未被检查的异常包含在方法声明中。
如果一个类中的许多方法出于同样的原因而抛出同一个异常,那么在该类的文档注释中对这个异常做文档,而不是为每个方法单独做文档,这是可以接受的。
第45条:在细节消息中包含失败--捕获信息
为了捕获失败,一个异常的字符串表示应该包含所有对该异常有的贡献的参数和域的值。
为了确保在异常的字符串表示包含足够的失败--捕捉信息,一种办法是在异常的构造函数中以参数形式引入这些信息。然后,有了这些信息,只要把它们放到消息描述中,就可以自动产生消息细节描述。
第46条:努力使失败保持原子性
一般而言,一个失败的方法调用应该使对象保持它在被调用之前的状态。具有这种属性的方法被称为具有失败原子性。
有几种途径可以获得这种效果。最简单的办法莫过于设计一个非可变的对象,如果一个对象是非可变的,那么失败原子性是显然的。如果一个操作失败了,它可能会阻止创建新的对象,但是永远也不会使已有的对象保持在不一致的状态中,因为当每个对象被创建之后它就处于一致的状态中,以后不会再发生变化。
对于在可变对象上执行操作的方法,获得失败原子性最常见的方法是,在执行操作之前检查参数的有效性。这可以使得在对象的状态被修改之前,适当的异常首先被抛出来。
public Object pop(){
if(size==0)
throw new EmptyStackException();
Object result=elements[--size];
elements[size]=null;
return result;
}
一种类似的获得失败原子性的办法是,对计算处理过程调整顺序,使得任何可能失败的计算部分都发生在对象状态被修改之前。如果对实参的检查只有执行了一部分计算之后才能进行的话,那么这种办法实际上是一种办法的自然扩展。
第三种获得失败原子性的办法没有那么常用,做法是编写一段恢复代码,由它来解释操作过程中发生的失败,以及使对象回滚到操作开始之前的状态上。这种办法主要用于永久性的数据结构。
最后一种获得失败原子性的办法是,在对象的一份临时拷贝上执行操作,当操作完成之后再把临时拷贝中的结果复制给原来的对象。如果数据被保存在临时的数据结构中,计算过程会更加快速,那么这种办法非常实用。
虽然失败原型总是期望的目标,但它并不是总是可以做的到。
即使在可以实现失败原子性的场合,它也并不总是所期望的。对于某些操作,它会显著的增加开销或者复杂性。然而一旦你知道了问题所在,那么获得失败原子性往往是简单而容易的。总结一条规则:作为方法规范的一部分,任何一个异常都不应该改变对象调用该方法之前的状态。如果这条规则被违反,则api文档应该清楚的指明对象将会处于什么样的状态,不幸的是,大量现在的api文档都未能做到这一点。
第47条:不要忽略异常
空的catch块会使异常达不到应有的目的,异常的目的是强迫你处理不正常的条件。至少catch块也应该包含一条说明,用来解释为什么忽略掉这个异常是合适的。
本条目中的建议同样适用于被检查的异常和未被检查的异常,不管一个异常代表了一个可遇见的例外条件,还是一个程序错误,用一个空的catch块忽略它将会导致程序在遇到错误的情况下悄然的执行下去。正确的处理异常能够避免无可挽回的失败。
try{
int i=0;
while(true)
a[i++].f();
}catch(ArrayIndexOutOfBoundsException e){
}
三个错误:
1.异常机制的设计初衷是用于不正常的情形,所以很少会有jvm实现试图对它们的性能做优化。所以,创建,抛出和捕获异常的开销是昂贵的。
。2.把代码放在try-catch块中反而阻止了现代jvm实现本来可能执行的某些特定的优化。
3.对数组进行遍历的标准模式并不会导致冗余的检查,有些现代的jvm实现会将它们优化掉。
基于异常的循环不仅模糊了代码的意图,降低了它的性能,而且它不能保证正常工作。
异常只应该被用于不正常的条件,它们永远不应该被用于正常的控制流。更一般的,你应该优先使用标准的,容易理解的模式,而不是那些声称可以提供更好性能的,弄巧成拙的模式。即使真的能够改进性能,面对jvm实现的不断改进,这种模式的性能优势也许不复存在。然而,由这种过渡聪明的模式带来的隐藏的错误,以及维护的痛苦却依然存在。
一个设计良好的api不应该强迫它的客户为了正常的控制流而使用异常。如果一个类具有一个状态相关的方法,即只有特定的不可预知的条件下才可以被调用的方法,那么这个类往往也应该有一个单独的状态测试方法,即只是是否可以调用第一个方法。例如,Iterator类有一个状态相关的next方法,它返回迭代过程中的下一个元素,对应的状态测试方法为hasNext。这使得使用标准模式称为可能。
第40条:对于可恢复的条件使用被检查的异常,对于程序错误使用运行时异常。
java提供三种可抛出结构:被检查的异常,运行时异常,和错误。
主要的原则是:如果期望调用者能够恢复,那么对于这样的条件应该使用被检查的异常。
有两种未被检查的可抛出结构:运行时异常和错误。在行为上两者是等同的;他们都是不需要也不应该被捕获的抛出物。如果一个程序抛出一个未被检查的异常或者一个错误,则往往是不可恢复的情形,继续执行下去有害无益。如果一个程序没有捕获这样的可抛出结构,将会导致当前线程停止,并伴以一个适当的错误消息。
用运行时异常来指明程序错误。大多数的运行时异常都是表明前提违例。所谓前提违例是指api的客户没有遵守api规范建立的约定。
你所实现的所有的为被检查的抛出结构都应该是RuntimeException的子类(直接或间接的)、最好不要实现Error子类。
总而言之,一般对于可恢复的条件使用被检查时异常,对于程序错误,使用运行时异常。
第41条:避免不必要的使用被检查的异常。
被检查的异常是java的一个很好特性,于返回代码不同,它强迫程序员处理例外的条件,大大提高了可靠性。然而,过分使用被检查的异常会使api用起来非常不方便。如果一个方法会抛出一个或者多个被检查的异常,那么调用该方法的代码必须在一个或者多个catch块中处理异常或则声明这些异常,以便让它们传播出去,无论哪个方法都给程序员添加了不可忽视的负担。
把被检查的异常变成未被检查的异常是一种技术是,把这个要抛出异常的方法分成2个方法,其中第一个方法返回一个boolean,表明是否应该抛出异常。例如:
try{
obj.action(args);
}catch(){
}
转化为:
if(obj.actionPermitted(args)){
obj.action(args);
}else{
}
这种转化并不是总是合适的,但是合适的地方,总会让api用起来更舒服。虽然后者调用序列没有前者漂亮,但是这样得到的api更加灵活。如果一个对象将会在缺少外部同步的情况下被并发访问,或者可被外界改变状态,那么这种状态是不合适的,因为在actionPermitted和action这两个调用的时间间隔中,对象状态有可能发生变化。
第42条:尽量使用标准的异常
重用现有的异常有很多好处
1.最主要的是,它使得你的api更加易于学习和使用,因为它与程序原来已经熟悉的习惯用法一致。
2。对于用到这些api的程序而言,他们的可读性更好,因为他们不会冲刺着程序员不熟悉的异常。
3.异常类越少,意味着内存占用越小,并且装载这些类的时间开销也越小。
最后,一定要清楚,选择重用哪一个异常并不总是一门精确的科学。
第43条:抛出的异常要适合于相应的抽象
高层的实现应该铺货底层的异常,同时抛出一个可以按照高层抽象进行解释的异常。这种做法被称为异常转译。例子:
try{
}catch(LowerLevelException e){
throw new HigherLevelexception();
}
一种特殊形式的异常转译被称为异常连接,如果底层的异常对于调用该异常被抛出的情形非常有帮助,那么使用异常连接是很适合的。在这种方法中,底层的异常被高层的异常保存起来,并且高层的异常提供一个公有的访问方法来获得底层的异常:
try{
}catch(LowerLevelException e){
throw new HigherLevelexception(e);
}
尽管异常转意比不加选择的传递底层异常的做法有所改进,但是它不能被滥用。如果可能的话,处理来自底层异常的最好做法是,在调用底层方法之前确保它们会成功执行,而避免它们会抛出异常。有时候,你可以在给底层传递实参之前,显示的检查这些实参的有效性,从而避免底层方法抛出异常。
如果无法阻止来自底层的异常,那么其次做法是让高层来处理这些异常,从而将高层方法的调用者于底层的问题隔离开。
如果既不能阻止来自底层的异常,也无法将它们与高层隔离开,那么一般做法是使用异常转译。只有在底层方法的规范碰巧可以保证它抛出的异常对于高层也是合适的情况下,才可以将异常从底层传播到高层。
第44条:每个方法抛出的异常都要有文档
描述一个方法所抛出的异常,是正确使用这个方法所需要文档的重要组成部分,因此,花点时间仔细的为每个方法抛出的异常做文档是特别重要的。
总是要单独的声明被检查的异常,并且利用javadoc的@throws标记,准确的记录下每个异常被抛出的条件。如果一个方法可能会抛出多个异常类,则不要使用“快捷方式”,即声明它会抛出这些异常类的某个超类。作为一个极端的例子,永远不要声明一个方法throws exception。这样的声明不仅没有为该程序员提供关于这个方法能够抛出哪些异常的任何指导信息,而且大大的妨碍了该方法的使用,因为它实际上掩盖了在同样的执行环境中该方法可能会抛出的任何其他异常。
使用javadoc的@throws标签记录下一个方法可能会抛出的每个未被检查的异常,但是不要使用throws关键字将未被检查的异常包含在方法声明中。
如果一个类中的许多方法出于同样的原因而抛出同一个异常,那么在该类的文档注释中对这个异常做文档,而不是为每个方法单独做文档,这是可以接受的。
第45条:在细节消息中包含失败--捕获信息
为了捕获失败,一个异常的字符串表示应该包含所有对该异常有的贡献的参数和域的值。
为了确保在异常的字符串表示包含足够的失败--捕捉信息,一种办法是在异常的构造函数中以参数形式引入这些信息。然后,有了这些信息,只要把它们放到消息描述中,就可以自动产生消息细节描述。
第46条:努力使失败保持原子性
一般而言,一个失败的方法调用应该使对象保持它在被调用之前的状态。具有这种属性的方法被称为具有失败原子性。
有几种途径可以获得这种效果。最简单的办法莫过于设计一个非可变的对象,如果一个对象是非可变的,那么失败原子性是显然的。如果一个操作失败了,它可能会阻止创建新的对象,但是永远也不会使已有的对象保持在不一致的状态中,因为当每个对象被创建之后它就处于一致的状态中,以后不会再发生变化。
对于在可变对象上执行操作的方法,获得失败原子性最常见的方法是,在执行操作之前检查参数的有效性。这可以使得在对象的状态被修改之前,适当的异常首先被抛出来。
public Object pop(){
if(size==0)
throw new EmptyStackException();
Object result=elements[--size];
elements[size]=null;
return result;
}
一种类似的获得失败原子性的办法是,对计算处理过程调整顺序,使得任何可能失败的计算部分都发生在对象状态被修改之前。如果对实参的检查只有执行了一部分计算之后才能进行的话,那么这种办法实际上是一种办法的自然扩展。
第三种获得失败原子性的办法没有那么常用,做法是编写一段恢复代码,由它来解释操作过程中发生的失败,以及使对象回滚到操作开始之前的状态上。这种办法主要用于永久性的数据结构。
最后一种获得失败原子性的办法是,在对象的一份临时拷贝上执行操作,当操作完成之后再把临时拷贝中的结果复制给原来的对象。如果数据被保存在临时的数据结构中,计算过程会更加快速,那么这种办法非常实用。
虽然失败原型总是期望的目标,但它并不是总是可以做的到。
即使在可以实现失败原子性的场合,它也并不总是所期望的。对于某些操作,它会显著的增加开销或者复杂性。然而一旦你知道了问题所在,那么获得失败原子性往往是简单而容易的。总结一条规则:作为方法规范的一部分,任何一个异常都不应该改变对象调用该方法之前的状态。如果这条规则被违反,则api文档应该清楚的指明对象将会处于什么样的状态,不幸的是,大量现在的api文档都未能做到这一点。
第47条:不要忽略异常
空的catch块会使异常达不到应有的目的,异常的目的是强迫你处理不正常的条件。至少catch块也应该包含一条说明,用来解释为什么忽略掉这个异常是合适的。
本条目中的建议同样适用于被检查的异常和未被检查的异常,不管一个异常代表了一个可遇见的例外条件,还是一个程序错误,用一个空的catch块忽略它将会导致程序在遇到错误的情况下悄然的执行下去。正确的处理异常能够避免无可挽回的失败。