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. 异常类型
异常类的根类:Throwable
异常类分3种类型:system error, exception, runtime exception
异常分类 | 含义 | 异常类 |
---|---|---|
System Error (Error类) | 极少发生,不可恢复,例如JVM机资源耗尽等 | LinkageError, VirtualMachineError |
Exception (Exception 类) | 程序本身或外部环境导致 | ClassNotFoundException, IOException |
Runtime Exception (RuntimeException 类) | 编程错误,如数组越界,转换错误 | ArithmeticException, NullPointerException, IndexOutOfBoundsException, IllegalArgumentException |
其中Error
, RuntimeException
以及它们的子类称为非检查异常,这类异常不可恢复。其他异常为检查异常, 即编译器强制要求在方法头中声明,或者在方法中用try
-catch
处理。
3. 异常处理
异常处理由3部分组成:声明异常(Declaring Exceptions),抛出异常(Throwing Exceptions),捕捉异常(Catching Exceptions)。
3.1 声明异常
每一个可能抛出异常的方法必须在方法头重声明异常,称为异常声明,例如:
public void myMethod() throws IOException
可以抛出多个异常:
public void myMethod() throws Exception1, Exception2, ..., ExceptionN
Error
和RuntimeException
(非检查异常) 不必声明。
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
之前有return
,finally
语句块仍然会执行。
可以用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提供了不少的异常类。 尽可能使用它们而不是定义自己的异常类。 但是,如果遇到预定义异常类无法充分描述的问题,则可以创建自己的异常类,这些异常类是从Exception
或Exception
的子类(如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.