异常

来源:互联网 发布:企业网络拓扑图及配置 编辑:程序博客网 时间:2024/05/16 08:00

 永远不需要为清理异常对象而担心,因为它们都是用new在堆上创建的对象,所以垃圾回收器会自动把它们清理掉。

1异常的概念

使用异常的一个明显好处就是,它能够降低错误处理代码的复杂度。与之前的错误处理方法相比,异常机制使得代码的阅读、编写和调试工作更加井井有条。

异常最重要的方面之一就是如果发生问题,它们将不允许程序沿着其正常的路径继续走下去。异常允许强制程序停止运行,并告诉我们出现了什么问题,或者强制程序处理问题,并返回到稳定状态。

2 自定义异常

如果要自己自定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承(不过这样的异常并不容易找)。通过新建立的异常类型最简单的办法就是让编译器为产生默认的构造器。对异常来说,最重要的部分就是类名,所以下面代码中的示例在大多数情况下已经够用了。

如下代码验证了如何用try catch捕获异常,验证了如何使用自定义的异常类,验证了执行了catch语句之后会执行finall代码块中的代码。

/*继承自Exception的自定义异常*/class simpleException extends Exception{}public class InherException {public void f() throws simpleException{System.out.println("从f方法中抛出异常");throw new simpleException();}public static void main(String[] args){InherException test = new InherException();try {test.f();} catch (simpleException e) {System.out.println("捕获到了simpleException异常");} finally{System.out.println("异常最后确实执行了finally块内的代码");}}}

运行结果如下:

3 异常说明

类似于public void f() throws simpleException{}这种在定义方法的时候在其名字后面加上可能抛出的异常类型throwssimpleException。称作是异常说明。

4 重新抛出异常

假如当前异常对象是Exception e,如果只是把当前异常对象e重新抛出,那么printStackTrace()方法显示的将是原来异常抛出的调用栈信息,而非重新抛出点的信息。想要更新这个信息,可以调用fillStackTrace()方法,抛出的是(Exception)e.fillStackTrace()这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的。(具体内容参见P258《Thinking in java》)

5 异常链

在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。

在Throwable的子类中只有三种基本的异常类提供了带case参数的构造器,它们是Error,Exception和RuntimeException。如果要把其他类型的异常链接起来,应该使用initCase()方法而不是构造器。

6 java标准异常

Throwable是被用来表示任何可以作为异常被抛出的类。Throwable对象可分为两种类型(指从Throwable继承而得到的类型):Error用来表示编译时和系统错误(一般不需要我们去关心这个错误,这种错误是属于JVM层次的严重错误,因此这种错误是会导致程序终止运行的);Exception是可以被抛出的基本类型,在Java类库、用户方法以及运行时故障中都可能抛出Exception异常。所以java程序员最关心的基本类型通常是Exception。
      Exception表示可恢复的异常,是编译器可以捕获到的,它包含两种类型:检查异常(checked exception)和运行时异常(runtimeexception).

(1)  检查异常是在程序汇总最经常碰到的异常。所有继承自Exception并且不是运行时异常的都是检查异常,比如最常见的IO异常和SQL异常。这种异常都是在编译时期,Java编译器强制程序去捕获此类的异常,即把这些可能出现异常的代码放到try块中,把对异常处理的代码放到catch块中。应用的情况有①异常的发生并不会导致程序出错,进行处理后可以继续执行后续的操作;②程序依赖于不可靠的外部条件,例如系统IO。

(2)  运行时异常:

RuntimeException及其子类是一个特例,产生此类异常不需要在方法声明的时候进行异常说明也不需要try catch处理(编译器并没有强制要求这么做),它们会被Java虚拟机抛出,这种异常属于错误,将被自动捕获。不需要异常处理RuntimeException及其子类异常的原因是:1)这类错误无法预料;2)这是应该在代码中进行检查的错误。最常见的运行时异常有NullPointerException(空指针异常)、ClassCastException(类型转换异常)、ArrayIndexOutOfBoundsException(数组越界异常)、ArrayStoreException(数组存储异常)、BufferOverflowException(缓冲区溢出异常)、ArithmeticException(算数异常)。

7 finally

Finally子句中的代码无论try块中的异常是否抛出,都可以得到执行(除非代码没有进try或者遇到了exit)。这通常适用于内存回收之外的情况。Finally非常重要,使得程序员保证,无论try块中发生了什么,内存总能得到释放。但java有垃圾回收机制,所以内存释放不再是问题。当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。这种需要清理的资源包括:已经打开的文件或网络连接,在屏幕上画的图形,甚至可以是外部世界的某个开关。

总结:不管是在try执行过程中遇到return、break还是continue,代码都是会执行该try对应的finally语句。

总结:不管是在try执行过程中遇到return、break还是continue,代码都是会执行其所在try对应的finally语句。

(1)   在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高一层的异常处理程序之前,执行finally子句。

class FourException extends Exception {}public class AlwaysFinally {public static void main(String[] args) {System.out.println("Entering fist try block");try {System.out.println("Entering second try block");try {throw new FourException();} finally {/* 内层抛出的异常在这没有被catch到 ,也要执行内层的finall块 */System.out.println("finally in 2nd try block");}} catch (FourException e) {System.out.println("捕获到内层try抛出到外层try的异常");} finally {System.out.println("finally in 1st try block");}}}

运行结果如下:



(2)   在return中使用finally

Return在何处不重要,总会执行finally内的代码。

public class ReturnFinally {public static int f(int i){System.out.println("进到f() i = "+i);try{switch(i){case 4 :System.out.println(i);return i;//case 3 :System.out.println(i);return i;case 2 :System.out.println(i);return i;case 1 :System.out.println(i);return i;}}finally{i = 100;System.out.println("finally 总会被执行");}return 99;}public static void main(String[] args) {for(int i=0;i<4;i++){int n  =  f(i);System.out.println("*********************"+n);}}}

运行结果如下:


在try块中执行到return语句时会把当前的返回值保存起来,当去执行finally块中的代码就算覆盖了i的值,但是返回的结果就是在该返回的那个地方的i值。

(1)对于基本数据类型是这样,return的就是真正在try中return的值;

(2)如果返回的是引用类型,则其对象就会有改变(引用不变,但其内容会改变)。

(3)testFinally3()方法验证了当在finally块中有return语句时,将会覆盖方法中其他地方的返回值。

如下代码所示。

public class TestReturnFinally {public static int testFinally1(){int result = 1;try{result = 2;return result;}catch(Exception e){return 0;}finally{result = 3;System.out.println(" 执行testFinally1方法中的finally块");}}public static StringBuffer testFinally2(){StringBuffer s = new StringBuffer("hello ");try{return s;}catch(Exception e){return null;}finally{s.append("world");System.out.println(" 执行testFinally2方法中的finally块");}}public static int testFinally3(){int result = 1;try{result = 2;return result;}catch(Exception e){return 0;}finally{/*当在finally块中有return语句时,将会覆盖方法中其他地方的返回值*/System.out.println(" 执行testFinally3方法中的finally块");return 3;}}public static void main(String[] args) {int val = testFinally1();System.out.println("val = "+val);StringBuffer ref = testFinally2();System.out.println("ref = "+ref);/*当在finally块中有return语句时,将会覆盖方法中其他地方的返回值*/int val2 = testFinally3();System.out.println("val2 = "+val2);}}

运行结果如下:


(3)   即使循环中代码被break,还是会执行本次循环内后面代码中的finally部分。如下代码在i=2时跳出循环但是还是会执行本次循环中finally块中代码。

public class BreakFinally {public static void main(String[] args) {for(int i=0;i<4;i++){try{if(i == 2){break;}System.out.println("i = "+i+" 在try内执行的代码");}finally{System.out.println("i = " +i+" 即使代码被break了,这个finally内的代码还是会被执行");}}}}

运行如下:


(4)   即使循环中代码被continue,还是会执行本次循环内后面代码中的finally部分。如下代码在i=2时执行了continue语句,但是还是会执行本次循环中finally块中代码。

public class ContinueFinally {public static void main(String[] args) {for(int i=0;i<4;i++){try{if(i == 2){continue;}System.out.println("i = "+i+" 在try内执行的代码");}finally{System.out.println("i = " +i+" 即使代码被break了,这个finally内的代码还是会被执行");}}}}

运行结果如下:


8  异常匹配

       抛出异常的时候,异常处理系统会按照代码的书写顺序找出最近的处理程序。找到匹配的处理程序之后,它就认为异常竟得到处理,然后就不再继续查找。查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序。这一点非常有用,因为如果决定在方法里加上更多派生异常的话,只要客户程序员捕获的是基类异常,那么它们的代码就无需更改。

9 使用异常处理的原因、原则和目标、优点

异常处理的原因:如果为每个方法所有可能发生的错误都进行处理的话,任务就会显得过于繁重,程序员也不一定会愿意这么做,这样的话就会导致错误被忽略。这就导致开发异常处理系统,其初衷是为了更好地方便程序员处理错误。

异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常”。

异常处理的重要目标就是把错误处理代码同错误发生的地点相分离。这样可以使得代码不会与错误处理逻辑混淆在一起,也更容易理解和维护。

异常处理的优点之一就是把错误代码和错误处理代码分开;另外一个优点就是所有的错误都以异常的形式进行报告,这也是java由于其他诸如C++这类语言的长处之一。

0 0