异常机制及其处理

一、异常

  异常就是一个表示阻止执行正常进行的错误或者情况。比如数组下标越界访问异常:ArrayIndexOutOfBoundsException;文件不存在异常:FileNotFoundException等等。

 

抛出异常与异常处理的例子:除数为0异常。

package demo;

import java.util.Scanner;

/**
 * Created by luts on 2015/11/28.
 */
public class QuotientWithException {
    public static void main(String[] args){
        Scanner input = new Scanner(System.in);

        System.out.println("输入两个数:");
        int num1 = input.nextInt();
        int num2 = input.nextInt();

        try {
            if (num2 == 0)
                throw new ArithmeticException("除数不能为0");
            System.out.println(num1 + " / " + num2 + " = " + (num1 / num2));
        }
        catch (ArithmeticException ex){
            System.out.println("除数不能为0");
        }

        System.out.println("除法运算结束");
    }
}

输出:

输入两个数:
3
0
除数不能为0
除法运算结束
输入两个数:
6
3
6 / 3 = 2
除法运算结束

上述例子中,try语句是正常执行情况下的代码,如果出现异常则抛出异常。catch语句是对异常情况的处理。当程序中抛出一个异常的时候,程序的正常流程就被破坏了。throw语句用于抛出一个异常,类似于方法的调用,其调用的是catch块。catch块处理完异常后,不返回throw语句,而是执行catch块的下一条语句。

一个try-throw-catch块的模板为

  try {
        Code to try;
        Throw an exception with a throw statement or from method if necessary;
        more code to try;
        }
    catch (type ex){
        Code to process the exception;
        }

1.1. 异常的类型

  

throwable类是所有异常类的根。所有异常类都直接或间接的继承自throwable类。可以通过Exception 或者Exception 的子类来创建自己的异常类。异常类大体分为三类:系统错误、异常和运行时异常。

  · 系统错误(system error):有java虚拟机抛出,用Error类表示。Error类描述的是内部系统错误,无法对错误进行处理。

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

  · 运行时异常(runtime exception):用RuntimeException类表示,描述的是程序设计的错误。

 

Error类的子类例子:

可能引起异常的原因
LinkageError 一个类对另一个类由某种依赖性,编译前者后,后者变得不兼容
VirtualMachineError Java虚拟机中断或者没有继续运行所必需的资源可用

 

Exception类的子类例子:

可能引起异常的原因
ClassNotFoundException 企图使用一个不存在的类。例如试图使用Java命令来运行一个不存的类或者程序要调用三个类文件,而只能找到两个,都会发生这种异常。
IOException 同输入/输出相关的操作,例如,无效的输入、读文件时超出文件尾、打开一个不存在的文件等。

RuntimeException类子类的例子:

可能引起异常的原因
ArithmetricException 一个整数除以0.  注意:浮点运算不抛出异常
NullPointerException 企图通过一个null引用变量访问一个对象
IndexOutOfBoundsException

数组下标越界

IllegalArgumentException 传递给方法的参数非法或者不合适

通常情况下,免检异常都会反映出程序设计上不可恢复的逻辑错误。为了避免过多使用try-catch块,java语言不允许编写代码捕获或声明免检异常

 

1.2. 异常的处理

  java的异常处理模型包括:声明一个异常、抛出一个异常和捕获一个异常

1.2.1 声明异常

  声明异常:每个方法都必须声明它可能抛出的必检异常的类型。因为任何代码都可能在程序中发生系统错误或者运行时错误,所以不要求显示声明Error和RuntimeException(免检异常)。声明异常的语法:

抛出一个异常:
public void myMethod() throws IOException

抛出多个异常:
public void myMethod() throws IOException1,throws IOException2, ..., throws IOExceptionN

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

1.2.2. 抛出异常

  检测一个错误的程序可以创建一个正确的异常类型的实例并抛出它,这就称为抛出异常。

例如程序调用方法时传递的参数不符合方法的签名,可以创建一个IllegalArgumentException的实例并抛出它:

IllegalArgumentException ex = new IllegalArgumentException("Wrong Arguments");
throw ex;


或者使用下列语句:
throw new IllegalArgumentException("Wrong Arguments");

注意:声明异常使用关键字throws,抛出异常使用关键字throw。

1.2.3. 捕获异常

使用try-catch块来捕获异常:

try{
    statements; //statemenrs that may throw exceptions
}
catch(Exception1 exVar1){
    handle for Exception1 ;
}
catch(Exception2 exVar2){
    handle for Exception2 ;
}

...

catch(ExceptionN exVarN){
    handle for ExceptionN ;
}

  · 如果在try块中没出现异常,则跳过catch块语句。

   · 如果try块中的某条语句抛出了一个异常,java就会跳过try块中剩下的语句,然后开始查找处理这个异常的代码的过程。处理这个异常的代码称为异常处理器;可以从当前方法开始,沿着调用方法的调用链,按照异常的反向传播方向找到这个处理器。从第一个到最后一个逐一检查catch块,判断是catch块中的异常实例是否是该异常对象的类型。如果是,就将该异常对象赋值给所声明的变量,然后执行catch块中的代码。如果没有发现异常处理器,java会退出这个方法,把异常传递给调用方法的方法,继续同样的过程来查找处理器。如果在调用的方法链中都找不到处理器,程序就会终止并且在控制台上打印出错信息。

   需要注意的是:

    ·  一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束。其他的catch子句不再有匹配和捕获异常类型的机会。

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

    · 子类的catch块必须出现在父类的catch块之前,否则会导致编译错误。正确的例子:

try{
   ....
}
catch (RuntimeException ex){
   ....
}
catch (Exception ex){
  ....
}

     · java强迫程序员必须处理必检异常。如果一个方法中声明了一个必检异常,就必须在try-catch块中调用它,或者在调用方法中声明要抛出的异常。

假设方法P1调用方法P2,P2可能会抛出一个IOException异常,那么代码编写有两种方式:

捕获异常:
void p1(){
    try{
        p2();
    }
    catch (IOException ex){
    ......
    }
}



或者抛出异常:
void p1() throws IOException {
    p2();    
}

 

 

一个完整的声明、抛出、捕获异常的例子:

 1 package demo;
 2 
 3 /*
 4  * 利用Circle类的setRadius方法操作如何声明、抛出和捕获异常
 5  * Created by luts on 2015/11/29.
 6  */
 7 public class CircleWithException {
 8 
 9     private double radius;  //圆的半径
10 
11     private static int numberOfObjects = 0;     //记录实例对象的个数
12 
13     //构建一个半径为1的圆
14     public CircleWithException(){
15         this(1.0);
16     }
17 
18 
19     //构建一个指定半径的圆
20     public CircleWithException(double newRadius){
21         setRadius(newRadius);
22         numberOfObjects++;
23     }
24 
25     //访问器
26     public double getRadius(){
27         return radius;
28     }
29 
30     //设置器,声明异常、抛出异常
31     public void setRadius(double newRadius) throws IllegalArgumentException{
32         if (newRadius >= 0)
33             radius = newRadius;
34         else throw new IllegalArgumentException("Radius cannot be negative");
35     }
36 
37     public static int getNumberOfObjects(){
38         return numberOfObjects;
39     }
40 
41     public double findArea(){
42         return radius * radius * Math.PI;
43     }
44 }
 1 package demo;
 2 
 3 /**
 4  * Created by luts on 2015/11/29.
 5  */
 6 public class TestCircleWithException {
 7     public static void main(String[] args){
 8 
 9         //捕获异常
10         try {
11             CircleWithException c1 = new CircleWithException(5);
12             CircleWithException c2 = new CircleWithException(-5);
13             CircleWithException c3 = new CircleWithException(0);
14         }
15         catch (IllegalArgumentException ex){
16             System.out.println(ex);
17         }
18 
19         System.out.println("Number of objects created: " + CircleWithException.getNumberOfObjects());
20     }
21 }

输出结果:

java.lang.IllegalArgumentException: Radius cannot be negative
Number of objects created: 1

1.2.4. 从异常中获取信息

  异常对象包含关于异常信息的有用信息,这些信息可以通过java.lang.Throwable类中的实例方法获得:

  · printStackTrace()方法在控制台上输出栈跟踪信息。

  · getStackTrace()方法提高方案来访问由 printStackTrace()打印输出的栈跟踪信息。

 

例如上述例子,将测试类修改如下:

 1 package demo;
 2 
 3 /**
 4  * Created by luts on 2015/11/29.
 5  */
 6 public class TestCircleWithException {
 7     public static void main(String[] args){
 8 
 9         //捕获异常
10         try {
11             CircleWithException c1 = new CircleWithException(5);
12             CircleWithException c2 = new CircleWithException(-5);
13             CircleWithException c3 = new CircleWithException(0);
14         }
15         catch (IllegalArgumentException ex){
16             ex.printStackTrace();
17             System.out.println("\n" + ex.getMessage());
18             System.out.println("\n" + ex.toString());
19 
20 
21             System.out.println("\nTrace Info Obtained from getStackTrace");
22             StackTraceElement[] traceElements = ex.getStackTrace();
23             for (int i = 0; i < traceElements.length; i++) {
24                 System.out.print("method " + traceElements[i].getMethodName());
25                 System.out.print("(" + traceElements[i].getClassName() + ":");
26                 System.out.print( traceElements[i].getLineNumber() + ")");
27             }
28 
29 
30            // System.out.println(ex);  //打印一个有关异常信息的短信息ex.toStrin()
31         }
32 
33         System.out.println("Number of objects created: " + CircleWithException.getNumberOfObjects());
34     }
35 }

控制台输出:

 1 java.lang.IllegalArgumentException: Radius cannot be negative    <-------printStackTrac()
 2 
 3     at demo.CircleWithException.setRadius(CircleWithException.java:34)   <--------getMessage() 
 4 Radius cannot be negative
 5     at demo.CircleWithException.<init>(CircleWithException.java:21)
 6 
 7     at demo.TestCircleWithException.main(TestCircleWithException.java:12)  <--------toString()
 8 java.lang.IllegalArgumentException: Radius cannot be negative
 9     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
10 
11     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)  <------using getStackTrace()
12 Trace Info Obtained from getStackTrace                                 
13     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
14 method setRadius(demo.CircleWithException:34)method <init>(demo.CircleWithException:21)method main(demo.TestCircleWithException:12)method invoke0(sun.reflect.NativeMethodAccessorImpl:-2)method invoke(sun.reflect.NativeMethodAccessorImpl:62)method invoke(sun.reflect.DelegatingMethodAccessorImpl:43)method invoke(java.lang.reflect.Method:483)method main(com.intellij.rt.execution.application.AppMain:144)Number of objects created: 1
15     at java.lang.reflect.Method.invoke(Method.java:483)
16     at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

 

 

 

1.2.5 finally子句

  finally子句:不管是否发生异常,都会执行的语句,其语法如下:

try {
   .....
}
catch (TheException ex){
  .......
}
finally {
  fianlStatements;
}

  任何情况下,finally块中的代码都会执行,不管try块中是否出现异常或是否捕获异常:

  · 如果try块中没有出现异常,执行finalStatements,然后执行try语句的下一条语句。

  · 如果try块中有一条语句会引起异常,并被catch块捕获,然后跳过try块中的其他语句,执行catch块和finally子句。执行try语句之后的下一条语句。

  · 如果try块中有一条语句引起异常,但是没有被任何catch块捕获,就会跳出try块中其他的语句,执行finally子句,并且将异常传递给该方法的调用者。

  即使在到达finally块之前有一个return语句,finally语句还是会执行。

 

finally例子:

 1 public class TestException {  
 2     public static void main(String args[]) {  
 3         int i = 0;  
 4         String greetings[] = { " Hello world !", " Hello World !! ",  
 5                 " HELLO WORLD !!!" };  
 6         while (i < 4) {  
 7             try {  
 8                 // 特别注意循环控制变量i的设计,避免造成无限循环  
 9                 System.out.println(greetings[i++]);  
10             } catch (ArrayIndexOutOfBoundsException e) {  
11                 System.out.println("数组下标越界异常");  
12             } finally {  
13                 System.out.println("--------------------------");  
14             }  
15         }  
16     }  
17 }

 

输出结果:

 Hello world !
--------------------------
 Hello World !! 
--------------------------
 HELLO WORLD !!!
--------------------------
数组下标越界异常
--------------------------

 

小结:

  ·  try 块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。

  ·  catch 块:用于处理try捕获到的异常。
  ·  finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。在以下4种特殊情况下,finally块不会被执行:
      1)在finally语句块中发生了异常。
      2)在前面的代码中用了System.exit()退出程序。
      3)程序所在的线程死亡。
      4)关闭CPU。

 

 

 try-catch-finally 规则(异常处理语句的语法规则):

 

  1)  必须在 try 之后添加 catch 或 finally 块。try 块后可同时接 catch 和 finally 块,但至少有一个块。

  2) 必须遵循块顺序:若代码同时使用 catch 和 finally 块,则必须将 catch 块放在 try 块之后。
  3) catch 块与相应的异常类的类型相关。
  4) 一个 try 块可能有多个 catch 块。若如此,则执行第一个匹配块。即Java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为

  某个异常类型或其子类的实例,就执行这个catch代码块,不会再执行其他的 catch代码块
  5) 可嵌套 try-catch-finally 结构。
  6) 在 try-catch-finally 结构中,可重新抛出异常。
  7) 除了下列情况,总将执行 finally 做为结束:JVM 过早终止(调用 System.exit(int));在 finally 块中抛出一个未处理的异常;计算机断电、失火、或遭遇病毒攻击。

 

try、catch、finally语句块的执行顺序:

  1) 当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;

 

  2)当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;

 

  3)当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句。

 

 1.2.6 重新抛出异常

  如果异常处理器没有处理某种异常,或者处理器只是希望它的调用者注意到该异常,java就允许异常处理器重新抛出异常。语法如下:

try{
   .....
}
catch(TheException ex){
  perform operations before exits;
  throw ex;  //重新抛出异常,以便调用者的其他处理器获得处理异常ex的机会。
}

 

异常链

 1 package demo;
 2 
 3 public class TestException {  
 4     public static void main(String args[]) {  
 5         try {
 6             method1();    //捕获method1中的异常
 7         }
 8         catch (Exception ex){
 9             ex.printStackTrace();  //打印输出异常,首先显示method1 中抛出的异常,然后显示method2中抛出的异常。
10         }
11     }  
12     
13     public static void method1() throws Exception{
14         try{
15             method2();   //捕获method2中的异常
16         }
17         catch(Exception ex){
18             throw new Exception("New info from method1", ex);  //抛出一个新的异常
19         }
20     }
21     
22     public static void method2() throws Exception{
23         throw new Exception("New info from metho2");  //抛出一个异常,该异常被method1 的catch块捕获
24     }
25 }

输出:

1 java.lang.Exception: New info from method1
2     at demo.TestException.method1(TestException.java:18)
3     at demo.TestException.main(TestException.java:6)
4 Caused by: java.lang.Exception: New info from metho2
5     at demo.TestException.method2(TestException.java:23)
6     at demo.TestException.method1(TestException.java:15)
7     ... 1 more

 

 

关于异常处理知识的深入知识,可以参阅:[深入理解java异常处理机制]

posted on 2015-11-29 15:14  Luts  阅读(1105)  评论(0编辑  收藏  举报