effectiveJava学习笔记:异常(一)

下面是对异常的详细解释

Java 中的异常和处理详解

只针对异常的情况才使用异常

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

       这段代码有什么作用?看起来根本不明显他没有真正被使用的原因是没有更好的进行优化。事实证明,作为一个要对数组元素进行遍历的实现方式,他的构思是非常拙劣的。当这个循环企图访问数组边界之外的第一个数组元素时,用抛出,捕获、忽略ArrayIndexOutOfBoundsException的手段来达到终止无限循环的目的。假定他与数组循环的标准模式是等价的,对于任何一个Java程序员来说,下面的标准模式一看就会明白:

for(Mountain m : range){ 
m.climb(); 
} 

那么,为什么有人会优先使用基于异常的模式,而不是用行之有效的模式呢?这是被误导了,他们企图利用Java的错误判断机制来提高性能,因为VM对每次数组访问都要检查越界情况,所以他们认为正常的循环终止测试被编译器隐藏了,但是在for-each循环中仍然可见,这无疑是多余的,应该避免。这种想法有三个错误:

一,因为异常机制的设计初衷使用于不正常的情形,所以很少会有JVM实现试图对它们进行优化,使得与显式的测试一样快速。

二,把代码放在try-cathch块中反而阻止了现代JVM实现本来可能要执行的某些特定优化。

三,对数组进行遍历的标准模式并不会导致冗余的检查,有些现代的JVM实现会将它们优化掉。

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

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

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

java提供了三种可以抛出的结构: 
1. 受检异常(checked exception) 
2. 运行时异常(runtime exception) 
3. 错误(error)

运行时异常和错误都是不需要也不应该被捕获的可抛出结构。如果程序抛出运行时异常或者错误,说明出现了不可恢复的情形,继续执行下去有害无益。如果没有捕捉到这样的结构,将会导致当前线程停止,并出现适当的错误消息。

使用原则: 
1. 如果期望调用者能够适当地恢复,对于这种情况就使用受检的异常。 
2. 用运行时异常来表明编程错误。 
3. 如果不清楚是否有可能恢复,则使用未受检异常。
 

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

异常的分类:
java.lang.Throwable
1.Error错误:JVM内部的严重问题。无法恢复。程序人员不用处理。
2.Exception异常:普通的问题。通过合理的处理,程序还可以回到正常执行流程。要求编程人员要进行处理。
3.RuntimeException:也叫非受检异常(unchecked exception).这类异常是编程人员的逻辑问题。应该承担责任。Java编译器不进行强制要求处理。 也就是说,这类异常再程序中,可以进行处理,也可以不处理。
4.受检异常(checked exception).这类异常是由一些外部的偶然因素所引起的。Java编译器强制要求处理。也就是说,程序必须进行对这类异常进行处理。

常见异常:
1)非受检的:NullPointerException,ClassCastException,ArrayIndexsOutOfBoundsException,ArithmeticException(算术异常,除0溢出)
2)受检:Exception,FileNotFoundException,IOException,SQLException.

受检异常强迫程序员处理异常条件。 
过分使用受检异常会是API使用起来非常不方便。

受检异常的使用场景:

1、如果正确地使用API并不能阻止这种异常条件的产生;
2、并且一旦产生异常,使用API的程序员可以立即采取有效的动作。
如果这两个条件都成立,这种负担就是正当的。否则就适用于未受检的异常。

优先使用标准的异常

Java平台类库提供了一组基本的未受检的异常,他们满足了绝大部分API的异常抛出异常。

为什么优先使用标准异常
1.它使你的API可读性更强,因为它与程序员习惯的用法一致。 
2.异常类越少,程序在类装载阶段的负担就越少,时间开销也越少。

怎么使用标准异常
常用的标准异常: 
IllegalArgumentException 参数不符合条件 
IllegalStateException 接受对象的状态异常 
NullpointerException 空指针异常 
IndexOutOfBoundsException 数组越界 
ConcurrentModificationException 并发修改异常 
UnsopportedOperationException 不支持操作异常

抛出与抽象相对应的异常

1.如何处理异常? 
方法B抛出了一个受检的异常 ,那么方法A在内部调用方法B时,面对方法B抛出的受检异常,可以选择继续抛出向上传播这个异常,也可以捕获这个异常进行处理。究竟是向上传播抛出,还是捕获处理呢?

2.处理异常方法

方法一:抛出与抽象想对应的异常。 
例如 
如果方法B抛出了NoSuchElementException这个受检异常,然而在方法A中调用方法B时,根据方法A中的逻辑,当遇到NoSuchElementException异常时,抛出一个IndexsOutOfBoundsException异常更为合适。那么就不应该选择向上传播抛出NoSuchElementException,而是应该选择捕获NoSuchElementException,然后抛出IndexsOutOfBoundsException。

更高层的实现应该捕获底层的异常,同时抛出可以按照高层抽象进行解释的异常。这种做法称为异常转译(exception translation)。

try {
    ...
} catch(LowerLevelException e) {
    throw new HigherLevelException(message , e);
}

方法二:避免底层异常出现。 
处理来自底层异常的最好做法是,在调用底层方法之前确保它们会成功执行。 
还是拿上面的当成例子,如果高层的A方法能够通过普通的判断语句保证底层的B方法永远也不会抛出异常,那么就可以不必处理B方法的异常。

方法三:绕开底层异常。 
如果无法避免低层异常,可以让更高层来悄悄地绕开这些异常,从而将高层方法的调用者与低层的问题隔离开来。使用适当地记录机制来将异常记录下来。 
对于上面的例子,如果A方法实在不能避开B方法,那么该怎么办呢,只需要try-catch底层的异常,然后偷偷地通过记录机制把异常记录下来,这样A就不用做其他异常处理了。

除非低层方法碰巧可以保证它抛出的所有异常对高层也合适才可以将异常从低层传播到高层。

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

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

因此,花点时间仔细为每个方法抛出的异常建立文档是特别重要的。
始终要单独地声明受检的异常,并且利用Javadoc的@throws标识,准确地记录下抛出每个异常的条件。
对于接口中的方法,在文档中记录下它可能抛出的未受检异常显得尤为重要,这份文档构成了该接口的通用约定的一部分。
 

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

程序为捕获的异常而失败,系统会自动打印该异常的堆栈轨迹,在堆栈中包含该异常的字符串表示法(它的toString方法结果,包含类名,消息细节), 在这种情况下,我们有时看到一长串类名和自动生成的错误消息无从下手,究竟错误出现在了哪里?

因此,为了捕获到异常,我们应该把异常中有价值的细节信息打印出来! 
例如: 
IndexOutOfBoundsException中并不是有String的构造器,而是有这样的构造器。

public class 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;
    }
}

Java平台没有广泛使用这种做法,但是这种做法值得推荐,原因很简单,它能够让程序员易于抛出异常捕获失败。 
这也与toString方法相似,认真写好了,以后用起来就很方便,是良好的编程习惯。

努力使失败保持原子性

失败原子性实现方法
1、对象为不可变对象
2、在执行操作之前检查参数的有效性。在对象状态被修改之前,先抛出异常
3、调整计算处理的过程,使得任何可能会失败的计算部分都在对象状态被修改之前发生
4、编写恢复代码,由其拦截操作过程中发生的失败,以及使对象回滚到操作开始之前的状态上
5、在对象的一份临时拷贝上执行操作,操作完成后,在使用临时拷贝的的结果代替对象的内容
 

不要忽略异常

空的catch块会使异常达不到应有的目的,至少catch块也应该包含一条说明,解释为什么可以忽略这个异常

猜你喜欢

转载自blog.csdn.net/qq_39071530/article/details/83652228