异常处理

Java的基本理念是:“结构不佳的代码不能运行”。

异常处理程序将“描述做什么事”和“出了问题怎么办”的代码相分离。

基本异常

“异常情形”是指 引发阻止当前方法或作用域继续执行的问题。

因为在当前环境下无法获得必要的信息来解决问题,所以能做的就是从当前的环境中跳出,并把问题提交给上一级环境。

当抛出异常后,首先同Java中的其他对象的创建一样,将使用new在堆上创建异常对象。然后,当前的执行路径被终止,并从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个“异常处理程序”来继续执行程序。

捕获异常

try{
     //code that might generate exception
}catch (Type1 id1) {
     //Handle exception of Type1
}catch (Type1 id2) {
     //Handle exception of Type2
}
//etc . . .
finally {
     //Activities that happen every time
}
两种模型

“终止模型”:程序无法返回到异常发生的地方继续执行

“恢复模型”:异常处理程序修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。(实践证明并不实用)

自定义异常

从已有的异常类继承,最简单的方法就是让编译器产生缺省构造器。异常的名字最重要。

class MyException extends Exception { }     //编译会自动调用基类的缺省构造器

也可以为异常类定义一个接受字符串参数的构造器

class MyException extends Exception { 
     public MyException( ) { }
     public MyException(String msg) {
          super(msg);
     }
}

异常说明

把方法可能会抛出的异常告知给客户端程序员

void f( ) throws TooBig, TooSmall {     // . . .     }

此外

void f( ) {     // . . .     }         
//表示此方法除了从  RuntimeException继承的异常不会抛出任何异常

代码必须与异常说明保持一致。如果方法里的代码产生了异常却没有处理,编译器会发现这个问题并提醒你:要么处理这个异常,要么就在异常说明中表明将产生异常。

另外,可以声明方法将抛出异常,实际上却不抛出。然后编译器相信了这个声明,并强制此方法的使用者像真的会抛出异常那样使用这个方法。
好处:为异常先占个位子,以后修改程序库时就无需修改已有代码。
在定义抽象基类和接口时这种能力很重要,这样其派生类或接口实现就能够抛出这些预先声明的异常。

这种在编译时被强制检查的异常叫“被检查异常”

捕获所有异常

catchException e){
     // . . .
}

通过catch异常类型的基类Exception,这将捕获所有异常。不过它将不会包括太多具体的信息,我们需要调用从Throwable继承过来的方法:

String getMessage( )
String getLocalizedMessage( )

或调用

String toString( )
void printStackTrace( )
void printStackTrace(PrintStream)
void printStackTrace(java.io.PrintWriter)

getClass( )可以返回此对象类型的对象
getName()可以查询这个Class对象的名称
重新抛出异常

有时希望把刚捕获的异常重新抛出,尤其是在使用Exception捕获所有异常的时候。

catch(Exception e){
     // . . .
     throw e;
     //throw e.fillStackTrace( );
}

此时,printStackTrace( )方法将显示原来这个异常抛出点的调用栈信息,而非重新抛出点的信息。要想更新这个信息,可以调用fillStackTrace( )方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个对象而建立的。

Throwable 是Exception的基类,所以可能抛出一个是Throwable而非Exception的对象,所以捕捉Exception的处理程序可能会捕捉不到这个对象。所以编译器会强制在异常说明里说明Throwable。

异常对象会自动被垃圾回收器清理掉,不用程序员去担心。

异常链

有时候希望在捕捉一个异常后抛出另一个异常,把原始异常信息保存下来,这被称为“异常链”。

解决办法:Throwable的子类在构造器中可以接受一个cause对象作为参数,这个cause对象就是原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的地方。

Throwable的子类中,只有Error、Exception和RuntimeException三个基本异常类提供了带cause参数的构造器。

如果要把其他异常链接起来,应该使用initCause()方法而不是构造器。

calss MyException extends Exception { }

class Test {
     public void f(Object o) throws MyException{
          if (o == null) {
               MyException e = new MyException( );
               e.initCause(new NullPointerException( ));
               throw e;
          }
          //code . . .
     }
}

Java标准异常

Throwable类被用来表示任何可以作为异常被抛出的类。Throwable对象可分为两种类型(指从Throwable继承而得到的类型):Error用来表示编译时和系统错误(除特定情况,一般不用你操心);Exception是可以被抛出的基本类型,在Java类库、用户方法以及运行时故障中都可能抛出Exception型的异常(这是你需要关心的)。

对异常来说,关键是理解概念以及如何使用。去看一遍Java文档,对异常有一个全面的理了解。

异常的基本概念是用名称代表发生的问题,并且异常的名称可以望文知意。

RuntimeException的特例

运行时异常会自动被Java虚拟机抛出,所以不必在异常说明中把它们列出来。这种异常叫做“未被检查异常”,即使在代码中抛出RuntimeException类型的异常,并且不去捕获它,它将直达main( ),在程序退出前调用异常的printStackTrace( )方法。

务必记住:只能在代码中忽略RuntimeException(及其子类)类型的异常,其他类型异常的处理必须由编译器强制实施。RuntimeException代表的是编程错误:

1)无法预料的错误。比如控制范围之外传进来的null引用
2)作为程序员应该在代码中进行检查的错误(比如数组越界)。在一个地方发生的异常,常常会在另一个地方导致错误。

Java的异常处理机制是被设计用来处理一些烦人的运行时错误,这些错误往往是由代码控制能力之外的因素导致的;它对发现某些编译器无法检测到的编程错误,也是非常重要的。

使用finally进行清理

当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。这种需要清理的资源包括:已经打开的文件或网络连接,在屏幕上的图形,甚至可以是某个外部世界的开关。

当涉及到break和continue语句的时候,finally子句也会得到执行。如果把finally子句和带标签的break及continue配合使用,可以达到goto效果。

用某些特殊的方式来使用finally子句,可能会造成异常丢失。

异常的限制

当覆盖方法时,只能抛出在基类方法的异常说明里列出的那些异常。这个限制使得基类的代码应用到其派生类对象时一样能够工作(面向对象的基本概念),异常也不例外。

1)异常的限制对构造器不起作用。但派生类构造器的异常说明必须包含基类构造器的异常说明。
2)如果派生类在扩展基类的同时又实现了接口中的同名方法,那么派生类中的方法就不能改变在基类中方法的异常接口。
3)覆盖后的方法可以不抛出任何异常,也可以抛出继承自基类方法异常接口的派生类。

反正必须满足在向上转型时,可以正确地捕获异常。

异常本身不属于方法类型的一部分。方法类型是由方法的名字和参数的类型组成的。不能基于异常说明来重载方法。

构造器

构造器会把对象设置成安全的初始状态,但还会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用特殊的清理方法之后才能得以清理。
如果在构造器内跑出了异常,这些清理行为也许就不会正常工作了。这意味着在编写构造器时需要格外小心。

鉴于刚刚学过finally,读者可能认为使用它就可以解决问题,但问题并非如此简单,因为finally会每次都执行清理代码,甚至在还不需要的时候也会执行。

因此,如果真的要用finally来进行清理的话,可以在构造器正常结束时设置某种标志,如果设定了这个标志,就不必在finally块中做任何清理。这种方法不是特别的优雅(这两处代码形成了耦合),因此除非没有别的办法,最好还是避免像这样在finally里进行清理。

异常匹配

抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。

查找的时候并不要求抛出的异常同处理程序锁声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序。

异常使用指南

  1. 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常)
  2. 解决问题并且重新调用产生异常的方法
  3. 进行少许修补,然后绕过异常发生的地方继续执行
  4. 用别的数据进行计算,以代替方法预计会返回的值
  5. 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层
  6. 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层
  7. 终止程序
  8. 进行简化
  9. 让类库和程序更安全

总结

改良的错误恢复机制是增强代码健壮性的最强有力的方式之一。

Java的最初目的就是用来建立让别人使用的构件。健壮的系统由健壮的构件组成。

通过使用异常来提供一致的错误报告模型,Java使构件能把错误信息可靠地通知给客户代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值