[Java] 异常

1. 概述

Java 的异常概念和C++的十分相似。
下面的例子求商,考虑到了除数为0的情况:

import java.util.Scanner;
public class QuotientWithMethod {
    public static int quotient(int number1, int number2) {
        if (number2 == 0) {  // 如果除数为0,则method自行退出
            System.out.println("Divisor cannot be zero");
            System.exit(1);
        }
        return number1 / number2;
    }
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        // Prompt the user to enter two integers
        System.out.print("Enter two integers: ");
        int number1 = input.nextInt();
        int number2 = input.nextInt();
        int result = quotient(number1, number2);
        System.out.println(number1 + " / " + number2 + " is " + result);
    }
}

但是终止程序应由调用函数作决定,所以以上程序改成下面这样:

import java.util.Scanner;
public class QuotientWithException {
    public static int quotient(int number1, int number2) {
        if (number2 == 0)
            throw new ArithmeticException("Divisor cannot be zero");
        return number1 / number2;
    }
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        // Prompt the user to enter two integers
        System.out.print("Enter two integers: ");
        int number1 = input.nextInt();
        int number2 = input.nextInt();
        try {
            int result = quotient(number1, number2);
            System.out.println(number1 + " / " + number2 + " is " + result);
        }
        catch (ArithmeticException ex) {
            System.out.println("Exception: an integer " + "cannot be divided by zero ");
        }
        System.out.println("Execution continues ...");
    } // end main
}

运行结果:

Enter two integers: 5 0
Exception: an integer cannot be divided by zero
Execution continues ...

2. 异常类型

Java 异常类层级结构

异常类的根类:Throwable
异常类分3种类型:system error, exception, runtime exception

异常分类 含义 异常类
System Error (Error类) 极少发生,不可恢复,例如JVM机资源耗尽等 LinkageError, VirtualMachineError
Exception (Exception 类) 程序本身或外部环境导致 ClassNotFoundException, IOException
Runtime Exception (RuntimeException 类) 编程错误,如数组越界,转换错误 ArithmeticException, NullPointerException, IndexOutOfBoundsException, IllegalArgumentException

其中ErrorRuntimeException以及它们的子类称为非检查异常,这类异常不可恢复。其他异常为检查异常, 即编译器强制要求在方法头中声明,或者在方法中用try-catch处理。

3. 异常处理

异常处理由3部分组成:声明异常(Declaring Exceptions),抛出异常(Throwing Exceptions),捕捉异常(Catching Exceptions)。

3.1 声明异常

每一个可能抛出异常的方法必须在方法头重声明异常,称为异常声明,例如:

public void myMethod() throws IOException

可以抛出多个异常:

public void myMethod() throws Exception1, Exception2, ..., ExceptionN

ErrorRuntimeException (非检查异常) 不必声明。

3.2 抛出异常

声明异常的关键字是throws, 抛出异常的关键字是throw

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

或者改成下面这样:

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

一般而言,Java API 中所有的异常类都有至少两个构造函数:一个无参构造函数,一个带有String类型参数的构造函数,此String参数称为异常消息(Exception Message),可以通过使用getMessage()函数获得。

3.3 捕捉异常

语法:

try {
    statements; // Statements that may throw exceptions
}
catch (Exception1 exVar1) {
    handler for exception1;
}
catch (Exception2 exVar2) {
    handler for exception2;
}
...
catch (ExceptionN exVarN) {
    handler for exceptionN;
}

寻找handler的过程称为捕捉异常。

抛出异常和捕捉异常:

void p1() {
    try {
        p2();
    }
    catch (IOException ex) {
        ...
    }
}
void p1() throws IOException {  // 声明异常
    p2();  // p2() 有可能抛出异常
}

捕捉多个异常的语法:

catch (Exception1 | Exception2 | ... | Exceptionk ex) {
    // Same code for handling these exceptions
}

4. finally语句块

不管异常有没有出现,finally语句块总是无条件执行。

try {
    statements;
}
catch (TheException ex) {
    handling ex;
}
finally {
    finalStatements;
}

即使finally之前有returnfinally语句块仍然会执行。
可以用finally代替catch

5. 何时使用异常

如果错误需要由其调用者处理,则方法应抛出异常。 try块包含在正常情况下执行的代码。 catch块包含在特殊情况下执行的代码。异常处理将错误处理代码与正常编程任务分开,从而使程序更易于阅读和修改。但是请注意,异常处理通常需要更多时间和资源,因为它需要实例化一个新的异常对象,回滚调用堆栈并通过调用的方法来传播异常以搜索处理程序。
异常在方法内产生,如果您希望异常由其调用者处理,就应该创建一个异常对象并抛出它。如果你能在出现异常的方法内处理异常, 就不需要抛出或使用异常。一般而言,项目中多个类中可能出现的常见异常应实现为异常类。单个方法中可能出现的简单错误最好内部处理,而不要抛出异常。这可以通过使用if语句来检查错误来完成。什么时候应该在代码中使用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());
else
    System.out.println("refVar is null");

哪些情况是异常的,哪些是预期的,有时难以确定。 重点在于不要滥用异常处理来处理简单的逻辑测试。

6. 重新抛出异常

如果异常处理程序(exception handler)不能处理异常或者仅仅想通知调用方法有异常产生,Java允许异常处理程序在这种情况下重新抛出异常,语法如下:

try {
    statements;
}
catch (TheException ex) {
    perform operations before exits;
    throw ex;
}

该语句重新抛出异常给调用方法,让调用方法中的其他handler处理ex

7. 链式异常(chained exception)

抛出一个异常和另一个异常形成一个链式异常。 上例中,catch块会重新抛出原始异常。 有时,您可能需要抛出一个新的异常(带有附加信息)以及原始异常。 这被称为链式异常。 如何创建并抛出链式异常的例子:

// ChainedExceptionDemo.java
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");
    }
}

8. 自定义异常类

您可以通过继承(extending) java.lang.Exception类来定义自定义异常类。 Java提供了不少的异常类。 尽可能使用它们而不是定义自己的异常类。 但是,如果遇到预定义异常类无法充分描述的问题,则可以创建自己的异常类,这些异常类是从ExceptionException的子类(如IOException)派生而来的。 在下例中,如果半径为负值,setRadius方法将抛出异常。 假设你希望将半径传递给处理程序,这种情况下可以自定义异常类:

// InvalidRadiusException.java
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;
    }
}

[1]Introduction to Java Programming 10th. Chapter 12.

猜你喜欢

转载自blog.csdn.net/ftell/article/details/80677182