异常

 
 
       Java标准异常的层次结构代表的分类绝对是一个败笔。因为所有的异常对象都是在运行时抛出的,没听说过有什么编译期异常。因此RuntimeException类代表的实际上是逻辑错误导致的异常,即标准c++中的std::logical_error。
       Java异常分为两大类,程序员只能抛出Exception异常对象,Error对象是Java系统在内部发生错误或者资源耗尽时才抛出的。
       还有另外一种分类方法,Error和RuntimeException对象被称为unchecked exception,其他的则被称为checked exception,也就是说java编译器将对checked exception检查是否提供了异常处理器(catch子句)。如果你的方法的异常规格表明可能会抛出一个checked exception,而你却没有使用catch子句处理这个可能抛出的checked exception,编译器会报错。
 
       当抛出异常时,如果希望调用者能够拦截并且恢复程序的执行,应该抛出checked exception(即可恢复情况使用checked exception)。为了让调用者能够恢复程序的执行,checked exception对象往往需要提供一些方法使得调用者可以获得一些有助于恢复的信息。所以,换位思考是个好主意,想一想如果调用者拦截了你抛出的异常,他能够恢复程序的执行,你就应该抛出checked exception,否则你就应该抛出RuntimeException.
       对于程序错误(比如引用为空,方法的前提条件不满足等不可恢复情况)应该抛出RuntimeException。
       注意,我们的方法不需要检查参数引用是否为空,Java运行时已经替我们做了。
       异常对象的传递机制是从异常抛出的地方往调用链的反方向传递。如果我们catch到了一个从底层抛出的异常,我们如何处理呢。如果我们能够处理该异常,我们应该处理该异常。如果我们不能处理该异常,需要继续传递时,我们需要考虑我们调用者能否理解这个底层传递过来的异常的含义。有时候调用者并不理解底层的异常,甚至都不知道有底层这个抽象设计存在,需要我们将异常进行转换,抛出调用者能够理解的异常对象。
       如果一个方法可能抛出checked exception和unchecked exception。你有两种做法,一是在异常规格中说明checked exception,然后在注释中使用@throws class description来说明unchecked exception。二是你决定避免使用异常规格,你就需要在@thorws说明所有的异常对象。
 
       在方法的后面加上 throws class1,class2 表示该方法肯能抛出class1或者class2对象作为checked exception。
       Java的异常规格说明和c++不同的地方是:
1)Java方法的异常规格只需要说明checked exception
2)Java方法内部如果抛出了一个checked exception,而没有在异常规格中描述该异常类型,编译器会检查并报错
3)调用具备异常规格的Java方法而不提供catch子句来处理对应的异常的行为会被编译器检查并报错
4)Java方法实现者不应该在异常规格中说明unchecked exception,而应该在方法内部防止抛出RuntimeException,对于Error则无须考虑。
5)出于性能以及其他原因,C++并不建议使用异常规格,(实际上C++世界目前已经承认异常规格是一个失败的主意,详细原因可以参考我的<<C++ Summary>>文章)。目前Java世界也出现了类似的声音,主要是认为大型工程中使用异常规格声明checked exception并不合适。因为有些时候并不需要添加不必要的catch块,而只是希望异常对象能够向上传递。在这种情况下可以使用RuntimeException的构造函数将一个checked exception转换成不需说明的RuntimeException对象,就可以避免对我们的方法使用异常规格。具体例子请参考<<Thinking in Java>>4 th P280代码。
 
 
其余Java和C++异常的区别
1)C++中可以抛出任何对象作为异常对象,Java中,必须抛出Throwable的子类对象
2)Throwable类的setCause和getCause方法提供了异常链机制,我们可以将catch到的异常对象交给新创建的异常对象,然后抛出新创建的异常对象
3)Java中没有catch(...)这样语句的必要,因为所有的异常都派生自Throwable类
4)Java中提供了finnally块,保证无论什么路径退出函数,都会执行一些必要的清理动作。C++需要依靠析构函数机制达到此目的。但是finnally块也会带来一些新的问题,比如:
       a)当try块中的对象构造时如果发生异常,就不应该执行finnally块中的对象的清理资源的方法,因为这个时候对象根本没有创建出来。这时候需要使用嵌套try/catch语句。
       b)当漏掉了catch块,只有finnally块,try块中抛出异常就丢失了。如果在这个finnally块中再抛出另一个异常,就会出现两个为处理的异常并存。
5)Java有异常堆栈跟踪,所有的Java异常对象都有printStackTrace方法。
6)C++重新抛出异常的语句是throw; 而Java则需要使用throw e;(e代表catch到的异常对象的引用)。
7)C++不允许前一个异常还未处理就抛出另一个异常(这是通常建议不要在析构函数中抛出异常的原因),而Java允许。不过这并不是Java的什么好特性。
 
       异常规格说明不是方法原型(方法类型,方法签名)的一部分,所以不要指望象下面这样提供方法f的两个重载版本:
public class Main extends Throwable{
    public void f() throws NullPointerException
    {
       
    }
   
    public void f() throws IllegalStateException
    {
   
    }
       编译器会报错:
init:
deps-jar:
Compiling 1 source file to /project/java/JavaApplication1/build/classes
/project/java/JavaApplication1/src/javaapplication1/Main.java:27: 已在 javaapplication1.Main 中定义 f()
    public void f() throws IllegalStateException
1 错误
生成失败(总时间: 1 秒)
 
       可以在 main 方法加上异常规格,并且不要使用 catch 块处理可能抛出的异常,这样就可以将异常直接传递给控制台。
       异常类需要从RuntimeException或者IOException派生。因为顶层类Throwable提供了一个带String参数的构造函数,用来支持toString功能,所以我们自己的异常类也应该提供一个带String参数的构造函数,并且调用父类的构造函数。
 
       派生类改写父类的方法时,需要遵守父类方法的异常规格说明,不能抛出父类方法的异常规格中没有说明的异常对象,当然可以在派生类的改写方法的异常规格说明中去除一些异常类。构造函数可以例外。
       这符合公有继承的WORKS-LIKE-A原则 。编译器会对此进行检查。
      
       当我们设计类的时候,通常我们优先考虑使用异常来报错。但是异常代表一种不正常情况,因此我们不能用来表达逻辑的分支。比如容器类的迭代器通常都有一个hasNext方法,用来测试是否到容器末尾。如果没有这个方法而改用异常的话,容器的设计者就强迫用户使用异常来检查容器是否已经没有元素。
       因此当我们设计API的时候,在需要抛出异常的地方要停下来想一想用户会怎么调用,这样会不会将异常强加到正常逻辑的分支中?是不是要提供一个对象状态测试方法?
       通常状态测试方法有两种,一种是单独提供一个方法测试状态,比如hasNext,还有一种是通过返回值,比如返回的引用是否为Null.后者效率更高,并且适合于多线程并发情况,而前者便于理解,可读性高。
       下面是关于系统设计时的错误设计的建议,由Herb Sutter提出,虽然是C++世界的建议,但是对Java同样适用:
错误设计要考虑:
1)  哪些情况属于错误 --- 定义错误的范围
观察代码最基本的执行单元—函数,错误大体可分为三类:
a)违反或者无法满足前条件
比如参数正确性(参数越界,指针是否为空)或某个状态约束
b)无法满足后条件
比如函数想返回一个值,但是却不能生成一个有效的返回值
c)无法重新建立不变式
对象成员的值和由成员所引用的对象的值汇集在一起就称为这个对象的状态。使一个对象的状态定义良好的性质就被称为它的不定式。
举个例子:
比如MyString类里有个char* _p成员变量,当构造函数执行成功后,_p指向一个长度`为_len的数组,并且_p[_len]==0。MyString的每个成员函数都必须保证重建这个不变式,直到最后销毁该对象。              
2)  错误的严重级别和分类
3)  模块边界错误如何传递
这里不是指C++异常传递,这是C++代码都应该使用的内部机制。问题是模块边界如何传递错误,比如网络通信程序,比如com+程序等等。
4)  哪些代码负责处理错误
5) 错误如何处理?写日志或通知用户还是短信通知等等
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值