第7章 异常处理
异常基础 异常是指程序在运行时发生的错误。通过异常处理机制,我们可以用一种结构化的可控方式来处理运行时地错误。尽管大多数高级程序设计语言都提供了一些异常处理机制,但是Java中的异常处理机制比其它语言提供的更简洁、更灵活。在Java中,所有的异常都用类来表示。所有的异常类都是从java.lang包中的Throwable类继承而来的。
Error和Exception 1.Error Error用来表示编译和运行错误,如程序进入了死循环、内存溢出等。Error只能在编译阶段解决,运行时程序本身无法解决,只能依靠其它程序的干预,否则会一直处于非正常状态。Error处理一般由系统承担,Java本身不提供相应的Error处理机制。 2.Exception Exception用来表示由程序活动所导致的错误,如运算时除数为0、打开一个文件时发现文件不存在等。当程序运行出现Exception时,在程序中加入异常处理代码,仍可使程序继续运行直至程序结束。 异常处理机制正是针对Exception类的
Throwable的方法 Throwable中定义了多个方法。因为所有的异常类都是Throwable的子类,所以它们都继承了Throwable的这些方法。 Throwable fillInStackTrace():返回一个包含调用栈信息的Throwable对象,该对象可以被重新抛出。 void printStackTrace():将调用栈信息输出到标准错误 。 String geiMessage():返回对异常的描述。 String getLocalizedMessage():返回对异常的本地描述。 String toString():返回一个包含异常描述的String对象。
异常的产生 Java中的异常处理机制看似十分的繁琐,实际使用时却并不复杂。下面介绍一个简单的基本异常: class Throw1{ public static void main(String args[]) { int a[]=new int[4]; System.out.println("Before exception!"); a[8]=12; //产生一个数组下标越界异常 System.out.println("After exception!"); }
我们可以看到,异常产生之后程序终止了执行,并且输出了相应的异常信息。此例中产生的异常有Java虚拟机自动抛出。 输出结果: Followed by exception! Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 8 at Throw1.main(Throw1.java:5) 上例中产生了一个数组下标越界异常,它是RuntimeException的一个子类,它是Java中的一个基本异常,要想对各种异常类有个全面的了解,需要去查看Java的JDK Documentation。 我们可以看到,异常产生之后程序终止了执行,并且输出了相应的异常信息。此例中产生的异常有Java虚拟机自动抛出。
捕获和处理异常 程序发生异常时,会生成一个异常对象,这个异常对象会在方法内部被抛出,它假设异常将被异常处理程序捕获。如果程序不捕获异常,它就要被JVM捕获,这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可以在方法内部设置一个特殊的块来监视可能产生异常的代码,然后让程序来捕获并处理异常对象从而不会影响其他语句的执行。在Java中,提供了try和catch语句来捕获和处理一个或多个异常。语法格式如下: try { // 可能产生异常的代码 } catch(Type1 e) { // 对异常类型Type1的异常处理代码 …….
其中,try块是一段可能产生异常的代码,也被叫做“监控区域”;catch语句的参数类似于方法的声明,包括一个异常类型和一个异常对象。异常类型必须为Throwable类的子类,它指明了catch语句所处理的异常类型,异常对象则在try块中产生;大括号中是异常处理程序,其中可以调用对象的方法。与一个try相关的catch语句可以有一个或多个,异常的类型决定了要执行哪个catch语句。也就是说,如果由一个catch语句指定的异常类型与发生的异常类型相符,那么就会执行这个catch语句,其他的catch语句则被跳过。
例如: class Try1{ public static void main(String args[]){ int a[]=new int[4]; try{ System.out.println("Before exception!"); a[8]=12; System.out.println("After exception"); } catch(ArrayIndexOutOfBoundsException e){ System.out.println("ArrayIndexOutOfBoundsException caught!"); catch(ArithmeticException e){ System.out.println("ArithmeticException caught!"); System.out.println("End up!");
输出结果: Before exception! ArrayIndexOutOfBoundsException caught! End up! 运行结果中可以看出,上例try块中产生了一个数组下标越界异常,这个异常对象被第一个catch语句捕获并处理,第二个catch语句则被直接跳过,紧接着执行了catch语句后面的输出语句。
如果try块中没有抛出异常,那么try块就会全部执行结束,并且会跳过它的所有catch语句,从最后一个catch后面的第一个语句继续执行。例如: class Try2{ public static void main(String args[]){ int a[]=new int[4]; try{ a[0]=12; System.out.println("No!"); } catch(ArrayIndexOutOfBoundsException e){ System.out.println("ArrayIndexOutOfBoundsException caught!"); System.out.println("End up!");
try代码块中可能会抛出一个或多个异常。我们要针对每个可能发生的异常,准备相应的异常处理程序。例如: 输出结果: Will exception be generated? No! End up! try代码块中可能会抛出一个或多个异常。我们要针对每个可能发生的异常,准备相应的异常处理程序。例如:
class Try3{ public static void main(String args[]){ int a[]={6,7,16,25,43,48,55,76}; int b[]={3,0,4,5,0,8}; for(int i=0;i<a.length;i++){ try{ System.out.println(a[i]+"/"+b[i]+"is"+a[i]/b[i]); } catch(ArithmeticException e){ System.out.println("Can't divide by zero!"); catch(ArrayIndexOutOfBoundsException e){ System.out.println("No matching element found!");
输出结果: 6/3 is 2 Can't divide by zero! 16/4 is 4 25/5 is 5 48/8 is 6 No matching element found!
捕获所有异常 当抛出异常的时候,Java会从上向下分别对每个catch语句处理的异常类型进行检测,然后按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它便认为异常将得到处理,然后就不在继续查找。查找的时候并不要求抛出的异常同处理程序所声名的异常完全匹配。派生类的对象也可以匹配其基类的处理程序。例如:
class Try4{ public static void main(String args[]){ int a[]={6,7,16,25,43,48,55,76}; int b[]={3,0,4,5,0,8}; for(int i=0;i<a.length;i++){ try{ System.out.println(a[i]+"/"+b[i]+"is"+a[i]/b[i]); } catch(ArrayIndexOutOfBoundsException e){ System.out.println("No natching element found!"); catch(RuntimeException e){ System.out.println("Some exception generated!");
输出结果: 6/3 is 2 Some exception generated! 16/4 is 4 25/5 is 5 48/8 is 6 No natching element found! 从运行结果中我们可以看到,数组下标越界异常被第一个catch语句捕获并处理,而算术异常却被RuntimeException类型的catch语句所捕获,这是因为ArithmeticException是RuntimeException的子类。因此,如果你既想捕获超类异常,又想捕获子类异常,那么就应该把子类的catch语句放在catch语句序列的前面。
很显然,我们也可以用一个catch语句处理多个异常类型,这时它的异常类型参数应该是这些异常类型的超类。在实际情况下,我们通常不能完全确定抛出异常的类型,为了保证程序的顺利运行,我们需要捕获所有可能类型的异常。通过捕获异常类型的基类Exception就可以做到这一点: catch (Exception e) { //………………… }
抛出异常 我们可以通过throw语句手动抛出异常。其基本语法格式如下: throw Obj; 其中throw是关键字,Obj是创建的异常类型的对象。在throw语句中同样是使用new创建异常类型的对象。例如: class Throw2{ public static void main(String args[]){ try{ System.out.println("Before throw!"); throw new ArithmeticException(); } catch(ArithmeticException e){ System.out.println("ArithmeticException caught!"); System.out.println("End up!");
输出结果: Before throw! ArithmeticException caught! End up!
重新抛出异常 由catch语句捕获的异常可以重新抛出以使外部catch语句可以捕获它。重新抛出的主要原因是为了允许多个异常处理程序访问异常。重抛异常会把异常抛给上一级环境中的异常处理程序,高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息。
class UseRethrow{ public static void t(){ try{ System.out.println("Bofore exception!"); throw new ArithmeticException(); } catch(ArithmeticException e){ System.out.println("ArithmeticException caught!"); throw e; class RethrowDemo{ public static void main(String args[]){ UseRethrow.t(); System.out.println("Caught again!");
ArrayIndexOutOfBoundsException在方法t()中被重新抛出,又在main()中被再次捕获。 输出结果: Before exception! ArithmeticException caught! Caught again! ArrayIndexOutOfBoundsException在方法t()中被重新抛出,又在main()中被再次捕获。
finally 对于一些代码,可能会希望无论try块中的异常是否被抛出,它们都能得到执行。为了在退出try/catch代码块时指定一个要执行的代码块,可以在异常处理程序后面加上finally子句。包含finally的try/catch的基本形式如下所示: try { // 可能产生异常的代码 } catch(Type1 e){ // 对异常类型Type1的异常处理代码 ……… Finally{ // 退出try/catch代码块后要执行的代码
class Usefinally2{ public static void t(){ int a[]=new int[4]; try{ System.out.println("Before exception!"); a[8]=10; } catch(ArrayIndexOutOfBoundsException e){ System.out.println("ArrayIndexOutOfBoundsException caught!"); throw e; finally{ System.out.println("In finally clause!"); class FinallyDemo2{ public static void main(String args[]){ Usefinally2.t(); System.out.println("Caught again!");
可见无论是出于何种原因,只要离开try/catch代码块,就会执行finally代码块。 输出结果: Before exception! ArrayIndexOutOfBoundsException caught! In finally clause! Caught again! 上例中内层try块抛出了异常,finally块中的语句得到了执行。再将程序修改一下。将程序中的这一行“a[8]=10;”改为“a[1]=10;”,此时没有异常抛出,运行结果如下: 可见无论是出于何种原因,只要离开try/catch代码块,就会执行finally代码块。
异常说明 在某些情况下,如果一个方法产生自己不处理的异常,它就必须在throws子句中声名该异常,这就是异常说明,它属于方法声名的一部分,紧跟在形式参数列表之后。包含throws子句的方法的基本形式如下: returnType methodname(/*argument list*/) throwsexceptionList{ //method body } 此处,exceptionList是一个方法可能会抛出的异常的列表,各个异常之间用逗号隔开。 凡是RuntimeException及其子类的异常都不必在列表中指定。它们被称为“未被检查的异常”。其它类型异常的处理都是由编译器强制实施的。这种异常被称为“被检查的异常”。
输出结果: class ThrowsDemo{ public static void t() throws Exception{ System.out.println("Before throw!"); throw new Exception(); } public static void main(String args[]){ try{ t(); catch(Exception e){ System.out.println("Exception caught!"); 输出结果: Before throw! Exception caught!
创建自定义异常 尽管Java的内置异常处理了大多数常见的错误,但是Java的异常处理机制并不局限于处理这些错误,我们可以自己定义异常类来表示程序中可能会遇到的特定问题。创建一个异常类很容易,只需定义一个Exception的子类即可。建立新的异常类型最简单的方法就是让编译器为你产生缺省构造器,所以这几乎不用写多少代码,还可以进一步自定义异常,比如可以为异常类定义一个接受字符串参数的构造器。例如:
输出结果: Before throw! class MyException1 extends Exception{} public class DefException1{ public static void t() throws MyException1{ System.out.println("Before throw!"); throw new MyException1(); } public static void main(String args[]){ try{ t(); catch(MyException1 e){ System.out.println("MyException1 caught!"); 输出结果: Before throw! MyException1 caught!