Java中的异常处理

来源:互联网 发布:网络直播营销策划方案 编辑:程序博客网 时间:2024/05/21 18:46

对于初学JAVA的菜鸟,往往对异常处理不是很清楚。本文比较全面浅显的介绍下异常处理。

1.异常处理的优势

异常处理最根本的优势是将检测错误(由被调用的方法完成)从处理错误(由调用方法完成)中分离出来。

2.异常类型

Java API 中有很多预定义的异常类,下图给出其中的一部分。

Throwable类是所有异常类的根。所有的Java异常类都直接或间接地继承自Throwable。可以通过扩展Exception或者Exception的子类来创建自己的异常类。
这些异常类可以分为三种主要类型:系统错误,异常和运行时异常。
     (1)系统异常(system error)是由Java虚拟机抛出的,用Error表示。Error类描述的是内部系统错误。这样的错误很少发生。如果发生,除了通知用户以及尽量稳妥地终止程序外,几乎什么也不做。
Error 类的子类的类子:

                                类                                                  可能引起异常的原因

                        LinkageError                               一个类对另一个类有某种依赖性,编译前者后,后者变得不兼容

                        VirtualMachineError                    Java虚拟机中断或者没有继续运行所必需的资源可用 

     (2)异常(exception)是用Eception类表示的,它描述的是由程序和外部环境引起的错误,这些错误能被程序捕获和处理。

Exception类的子类的例子:

                                类                                                   可能引起异常的原因

                       ClassNotFoundException              企图使用一个不存在的类。例如,如果试图使用命令java来运
                                                                             行一个不存在的类,或者程序要调用三个类文件而只能找到两
                                                                             个,都会发生这种异常

                       IOException                                   同输入/输出相关的操作,例如,无效的输入、读文件时超过
                                                                             文件尾、打开一个不存在的文件等。IOException的子类的例
                                                                             子有InterruptedIOException、EOFException 和
                                                                             FileNotFoundException

      (3)运行时异常(runtime exception)是用RuntimeException类表示的,它描述的是程序设计错误,例如,错误的类型转换、访问一个越界的数组或数值错误。运行时异常通常是由Java虚拟机抛出的。
RuntimeException类的子类的例子:

                               类                                                     可能引起异常的原因

                       ArithmeticException                        一个整数除以0。注意,浮点数的算数运算不抛出异常。

                       NullPointerException                      企图通过一个null引用变量访问一个对象

                       IndexOutOfBoundsException         数组的下标超出范围
                       
                       IllegalArgumentException               传递给方法的参数非法或不合适 

RuntimeException、Error以及它们的子类都称为免检异常(unchecked exception)。所有其他异常都称为必检异常(checked exception),意思是指编译器会强制程序员检查并处理它们。
       在大多数情况下,免检异常都会反映出程序设计上不可恢复的逻辑错误。例如,如果通过一个引用变量访问一个对象之前并未将一个对象赋值给它,就会抛出NullPointerException异常;如果访问一个数组的越界元素,就会抛出IndexOutOfBoundsException异常。这些是程序中必须纠正的逻辑错误。免检异常可能在程序中的任何一个地方出现。未避免过多的使用try-catch块,Java语言允许不必编写代码捕获或声明免检异常。

3.Java异常处理三种操作:声明异常、捕获异常、抛出异常

(1).声明异常

为了在方法中声明一个异常,就要在方法头中使用关键字,如下:
public void myMethod() throws IOException

如果方法可能抛出多个异常,就可以在关键字throws后添加一个逗号分隔异常列表:
public void myMethod() throws Exception1, Exception2, ..., ExceptionN

注意 *   如果方法没有在父类中声明异常,那么就不能在子类中对其覆盖来声明异常。

(2).抛出异常

       检测一个错误的程序可以创建一个正确类型的实例并抛出它。这就是抛出一个异常。比如程序发现传递给方法的参数与方法的合约不符,这个程序就可以创建IllegalArguemntException的一个实例并抛出它,如下:
IllegalArgumentException ex = new IlleagalArgumentException("Wrong Argument");throw ex;

也可以使用下面的语句:
throw new IlleagalArgumentException("Wrong Argument");

(3).捕获异常

      可以在try-catch块中捕获和处理异常。如下:
try{statements;// Statements that may throw exceptions}catch(Exception1 exVar1){// handler for exception1;}catch(Exception2 exVar2){// handler for exception2;}catch(Exception3 exVar3){// handler for exception3;}

如果在执行try块的过程中没有出现异常,则跳过catch子句
如果在try块中的某条语句抛出一个异常,Java就会跳过try快的剩余语句,然后开始检查处理这个异常的代码的过程。处理这个异常的代码成为异常处理器(exception handler);可以从当前的方法开始,沿着方法调用链,按照异常的反向传播方向找到这个处理器。从第一个到最后一个逐个检查catch块,判断在catch块中的异常实例是否是该异常对象的类型。如果是,就将该异常对象赋值给所声明的变量,然后执行catch块中的代码。如果没有发现异常处理器,Java会退出这个方法,把异常传递给调用这个方法的方法,继续同样的过程来查找处理器。如果在调用的方法链中找不到处理器,程序就会终止并且在控制台上打印出错信息。寻找处理器的过程称为捕获一个异常(catching an exception)。
如下:
main method{...try{...invoke method1;statement1;}catch(Exception1 ex1);{Process ex1;}statement2;}

method1{...try{...invoke method2;statement3;}catch(Exception2 ex2){Process ex2;}statement4;}

method2{...try{...invoke method3;statement3;}catch(Exception3 ex3){Process ex3;}statement6;}

method3 抛出一个异常

假设main方法调用method1, method1调用method2, method2调用method3, method3抛出一个异常,如上。考虑下面的情况:
         1)如果异常类型是Exception3,它就会被method2中的处理异常ex3的catch块捕获。跳过statement5,然后执行statement6.
         2)如果异常类型是Exception2,则退出method2,控制被返回给method1,而这个异常就会被method1中的处理异常ex2的catch块捕获。跳过
               statement3,然后执行statement4.
         3)如果异常类型是Exception1,则退出method1,控制被返回给main方法,而这个异常就会被main方法中处理异常ex1的catch块捕获。跳过
               statement1,然后执行statement2.
         4)如果异常没有在method2、method1和main方法中被捕获,程序就会终止。不执行statement1和statement2.

注意 * 从一个通用父类可以派生出各种异常类。如果一个catch块可以捕获一个父类的异常对象,它就能捕获那个父类的所有子类的异常对象。

注意 * 在catch块异常被指定的顺序是非常重要的。如果父类的catch块出现在子类的catch块之前,就会导致编译错误。例如,下面A的代码顺序是错误的,因为RuntimeException是Exception的一个子类。正确的顺寻是B。
// Atry{...}catch(Exception ex){...}catch(RuntimeException ex){...}

// Btry{...}catch(RuntimeException ex){...}catch(Exception ex){...}

注意 * Java强迫程序员处理必检异常。如果方法声明了一个必检异常,就必须在try-catch块中调用它,或者在调用方法中声明要抛出异常。例如,假定方法p1调用方法p2,而p2可能会抛出一个必检异常(例如, IOException),就必须按下图A和图B所示编写代码。
// Avoid p1(){try{p2();}catch(IOException ex){...}}

// Bvoid p1() throws IOException{p2();}

4.从异常中获取信息

异常对象包含关于异常的有价值的信息。可以利用下面这些java.lang.Throwable类中的实例方法获取有关的异常的信息。

java.lang.Throwable

getMessage(): String                               返回这个对象的消息
toString(): String                                        返回以下三个字符串的链接:(1)异常类的全名;(2)“:”
                                                                     (冒号或空格)(3)getMessage()方法
printStacTrace(); void                               在控制台上打印Throwable对象以及它的调用栈的跟踪信息
getStackTrace():StackTraceElement[]   返回栈跟踪元素构成的数组来表示这个可抛出的栈跟踪信息

下面给出一个例子:
public class TestException  {  public static void main(String[] args) {    try {      System.out.println(sum(new int[] {1, 2, 3, 4, 5}));    }    catch (Exception ex) {      ex.printStackTrace();      System.out.println("\n" + ex.getMessage());      System.out.println("\n" + ex.toString());      System.out.println("\nTrace Info Obtained from getStackTrace");      StackTraceElement[] traceElements = ex.getStackTrace();      for (int i = 0; i < traceElements.length; i++) {        System.out.print("method " + traceElements[i].getMethodName());        System.out.print("(" + traceElements[i].getClassName() + ":");        System.out.println(traceElements[i].getLineNumber() + ")");      }    }  }  private static int sum(int[] list) {    int result = 0;    for (int i = 0; i <= list.length; i++)      result += list[i];    return result;  }}

输出的信息如下图:


5.finally子句

有时候,不论异常是否出现或者被捕获,都希望执行某些代码块。finally子句可以实现这个目的。语法如下:
try{statement;}catch(TheException ex){// handling ex;}finally{// finalStatements;}

6.何时使用异常

try块包含正常情况下执行的代码。catch块包含异常情况下执行的代码。异常处理将错误处理代码从正常的程序设计任务中分离出来,这样,可以使程序更易读和更易修改。但是,应该注意,由于异常处理需要初始化新的异常对象,需要从调用栈返回,而且还需要沿着方法调用链来传播异常以便找到它的异常处理器,所以,异常处理通常需要更多的时间和资源。
        异常出现在方法中。如果想让该方法的调用者处理异常,应该创建一个异常对象并将其抛出。如果能在发生异常的方法中处理异常,那么就不需要抛出或使用异常。
一般来说,一个项目中多个类都会发生的共同异常应该考虑作为一种异常类。对于发生在个别方法中的简单错误最好进行局部处理,无须抛出异常。
         在代码中,什么时候应该使用try-catch块呢?当必须处理不可预料的错误状况时应该使用它。不要用try-catch块处理简单的、可预料的情况。如下:
try{System.out.println(refVar.toString());}catch(NullPointerException ex){System.out.println("refVar is null");}

最好用以下代码代替:
if(refVar != null)System.out.println(refVar.toString());elseSystem.out.println("refVar is null");

哪些情况是异常的,哪些情况是可预料的,有时候很难判断。但有一点要把握住,不要把异常处理用作简单的逻辑测试。


7.重新抛出异常

如果异常处理器没有处理异常,或者处理器只是希望它的调用者注意到该异常,Java就允许异常处理器重新抛出该异常。语法如下:
try{statement;}catch(TheException ex){// perform operations berfore exists;throws ex;}

语句throws ex重新抛出异常给调用者,以便调用者的其他处理器获得处理异常ex的机会。


8.链式异常

有时候,可能需要同原始异常一起抛出一个新异常(带附加信息)。这称为链式异常(chained exception)。下面给出一个例子:
public class ChainedExceptionDemo {  public static void main(String[] args) {    try {      method1();    }    catch (Exception ex) {      ex.printStackTrace();    }  }  public static void method1() throws Exception {    try {      method2();    }    catch (Exception ex) {      throw new Exception("New info from method1", ex);    }  }  public static void method2() throws Exception {    throw new Exception("New info from method2");  }}

执行结果如下图:

main方法调用method1(第四行),method1调用么method2(第十三行),method2抛出一个异常(第二十一行)。该异常被method1的catch块所捕获,并在第16行包装成一个新异常。该异常被抛出,并在main方法中的第4行的catch中被捕获。示例输出在第7行中的printStackTrace()方法的结果。首先,显示method1中抛出的异常,然后显示method2中抛出的原始异常。


9.创建自定制异常类

Java提供相当多的异常类,尽量使用它们而不要创建自己的异常类。然而,如果遇到一个不能用预定义异常类描述恰当的问题,那就可以通过派生Exception类或其子类来创建自己的异常类。示例如下:
public class InvalidRadiusException extends Exception {  private double radius;  /** Construct an exception */  public InvalidRadiusException(double radius) {    super("Invalid radius " + radius);    this.radius = radius;  }  /** Return the radius */  public double getRadius() {    return radius;  }}

第六行调用父类的带有一条消息的构造方法。这条消息将会被设置在异常对象中,并且可以通过在对象上调用getMessage()获得。

注意 * 不要扩展RuntimeException声明一个自定制异常类。因为这会使自定制异常成为免检的。最好使用自定制必检异常类。
原创粉丝点击