#Java异常处理

474 查看

传统的语言如何处理

在一些传统的语言(如C语言中)

  • if语句来判断是否出现了例外

  • 全程变量ErrNo

但这有几个缺点

  • 正常处理与异常处理的代码同样处理

  • 可读性(readability)差

  • 每次调用一个方法时都进行错误检查

  • 可维护性( maintainability )差错误由谁处理不请

  • 职责不清

Java中的异常处理特点:

Java中处理异常包括

  1. 抛出(throw)异常

  2. 运行时系统在调用栈中查找

    • 从生成异常的方法开始进行回溯,直到找到:

  3. 捕获(catch) 异常的代码
    调用过程如下所示:

代码

例子一

没有使用Java异常处理机制

public class Test {
    public static void main(String [] args){
        for(int i = 0; i < 2;i++){
            System.out.print(i + " ");
            System.out.println(1/0);//throw an exception.
        }
    }
}

运行结果:

0 Exception in thread "main" java.lang.ArithmeticException: / by zero

1/0明显非法,所以产生了异常ArithmeticException对象,这个对象是Exception的子类。

例子二

以下开始用异常处理机制捕获该异常:

public class Test {
    public static void main(String [] args){
        for(int i = 0; i < 2;i++){
            System.out.print(i + " ");
            try{
                System.out.println(1/0);//An exception will throw from here.
            }
            catch(Exception ex){//在这里,这个Exception 其实就是ArithmeticException
            //这里用到了程序运行时的多态思想。
            //实际中应指定具体的作为ex的类型,更加有针对性。
                System.out.println("exception handling...");//
            }
        }
    }
}

运行结果:

0 exception handling...
1 exception handling...

这样确实能捕获到相应的异常对象。尽管什么也没做(只是打印字符串),却让编译器不再报告之前的异常。因为上述catch(type ex){...}就是负责处理异常。

例子三

当捕获到异常后,程序的运行过程会怎样?

public class Test2 {
    public static void main(String[] args){
        try{
            for (int i = 0; i< 2; i++){
                System.out.print(i + " ");
                System.out.println(1/0);//exception being thrown from here.Then it wil jump directly to the corresponding catch block.
                System.out.println("This line is supposed to be ignored");
            }
        }
        //handling the exception.
        catch(Exception ex){
            System.out.println("Exception handling...");
        }
        System.out.println("End of the main()");
    }
}

运行结果:

0 exception handling...
End of the main()

for不可能循环到i = 2;因为在i = 1时已经抛出了异常。只要产生了异常,转入对应的catch(type ex){...}catch(type ex)必须在参数里面说明捕获的对象是哪类型的。
throw语句就像一个调用函数,当程序运行中抛出了一个异常对象,就会调用对应的catch(type ex){}来处理。但它又不像调用函数。因为在调用完后,它不会返回到throw语句,而是接着catch之后的语句。所以System.out.println("This line is supposed to be ignored");这条语句被没有执行;for循环也相当于被中断了。

Java的异常处理语句

抛出异常

throw 异常对象;

捕获异常

try{
    语句组
}
catch(异常类名 异常形式参数名){
    异常处理语句组; 
}
catch(异常类名 异常形式参数名){
    异常处理语句组; 
}
finally{
    异常处理语句组;
}

其中,catch语句可以0至多个,可以没有finally语句

异常的分类

Throwable

  • Error: JVM的错误(一般来说,我们很难处理这里异常)

  • Exception: 异常(重点关注)
    注:一般所说的异常,是指Exception及其子类

Exception类

构造方法

public Exception();
public Exception(String message);
Exception(String message, Throwable cause) ;

方法

getMessage()
getCause()
printStackTrace()

代码

例子一

仍然是没有使用Java的异常处理机制:

import java.util.Scanner;
public class QuotientWithMethod {
    public static int quotient(int num1, int num2){
        if(num2 == 0){
            log("Divisor cannot be zero");
            System.exit(1);
        }
        return num1/num2;
    }
    public static void log(String s){
        System.out.println(s);
    }
    public static void main(String[] args){
        Scanner input = new Scanner(System.in);
        //prompt user to enter two integers
        int num1 = input.nextInt();
        int num2 = input.nextInt();
        int result = quotient(num1,num2);//调用函数
        System.out.println(num1 + "/" + num2 + "is" + result );
    }
}

运行结果

input two numbers:
1 0
Divisor cannot be zero

在没用使用异常处理机制的情况下,出现了异常情况的话,在被调用的函数中,处理异常情况——这里是直接退出了程序;

例子二

用异常处理机制的好处是职责分明:被调函数中抛出异常,主调函数中负责处理异常。

import java.util.Scanner;
public class QuotientWithException {
    public static int  quotient(int num1, int num2){
        if(num2 == 0){
            throw new ArithmeticException("Divisor cannot be zero");//用关键字throw抛出异常对象,这里是调用构造器来新建对象
        }//但不做处理
        return num1/num2;
    }
    public static void main(String[] args){
        Scanner input = new Scanner(System.in);
        System.out.println("input two numbers:");
        int num1 = input.nextInt();
        int num2 = input.nextInt();
        boolean goOn = true;
        do {
            try {
                int result = quotient(num1, num2);
                System.out.println("num1" +"/" + "num2" + "is" +result);
                goOn = false;
            }
            //职责分明;一个负责抛出异常,一个负责处理异常.
            catch(ArithmeticException ex){
                //在主调函数中处理异常
                System.out.println("Exception occur:" + "Divisor cannot be zero. \n input again,please:");
                num1 = input.nextInt();
                num2 = input.nextInt();
                //int result = quotient(num1, num2);
                //System.out.println("num1" +"/" + "num2" + "is" +result);
            }
        }while(goOn);
        //处理后,进入catch{}后的语句.
        System.out.println("End of main().");
    }
}

运行结果

input two numbers:
1 0
Exception occur:Divisor cannot be zero. 
 input again,please:
1 1
num1/num2is1
End of main().

在被调函数中,只负责抛出异常:throw new ArithmeticException("Divisor cannot be zero");,主调函数中catch(ArithmeticException ex){...}指定将要捕获的对象的类型,并做相应的处理,这里要求重新输入。

多异常的处理

  • 子类异常要排在父类异常的前面,也就是先处理具体的异常,后处理抽象的异常。

  • finally语句,无论是否有异常都要执行,即使其中有break,return等语句

  • 在编译时,finally部分代码生成了多遍

Exception分两种

RuntimeException及其子类,可以不明确处理;否则,称为受检的异常(checked Exception)

RuntimeException, Error, and their subclasses are known as unchecked exceptions. All other exceptions are known as checked exceptions, meaning that the compiler forces the programmer to check and deal with them in a try-catch block or declare it in the method header.--Introduction to Java

对于受检的异常(checked Exception),要求明确进行语法处理:

  • 要么捕(catch)

  • 要么抛(throws):在方法的签名后面用throws xxxx来声明

    • 在子类中,如果要覆盖父类的一个方法,若父类中的方法声明了 throws异常,则子类的方法也可以throws异常

    • 可以抛出子类异常(更具体的异常),但不能抛出更一般的异常

自定义异常类

创建用户自定义异常时

  • 继承自Exception类或某个子Exception类

  • 定义属性和方法,或重载父类的方法

    • 这样getMessage(), toString(), printStackTrace()都会从Exception继承下来。

重抛异常及异常链接( Chained Exceptions)

对于异常,不仅要进行捕获处理,有时候还需要将此异常进一步传递给调用者,以 便让调用者也能感受到这种异常。这时可以在catch语句块或finally语句块中采取,以下三种方式:

  • 将当前捕获的异常再次抛出:
    throw e;

  • 重新生成一个异常,并抛出,如:
    throw new Exception("some message");

  • 重新生成并抛出一个新异常,该异常中包含了当前异常的信息,如:throw new Exception("some message",e);e.getCause() 来得到内部异常