异常处理
本章思维导图
Java的异常处理机制主要依赖于try、catch、finally、throw和throws五个关键字,其中try关键字后紧跟一个花括号括起来的代码块(花括号不可省略),简称try块,它里面放置可能引发异常的代码。catch后对应异常类型和一个代码块,用于表明该catch块用于处理这种类型的代码块。多个catch后还可以跟一个finally块,finally块用于回收在try块里打开的物理资源,异常机制会保证finally块总被执行。throws关键字主要在方法签名中使用,用于声明该方法可能抛出的异常;而throw用于抛出一个实际的异常,throw可以单独作为语句使用,抛出一个具体的异常对象。
Java7进一步增强了异常处理机制的功能,包括带资源的try语句、捕获多异常的catch两个新功能。
Java将异常分为两种,Checked异常和Runtime异常,Java认为Checked异常都是可以在编译阶段被处理的异常,所以它强制程序处理所有的Checked异常;而Runtime异常则无须处理。Checked异常可以提醒程序员需要处理所有可能发生的异常。
异常处理机制
Java的异常处理机制可以让程序具有极好的容错性,让程序更加健壮。当程序运行出现意外情况时,系统会自动生成一个Exception对象来通知程序,从而实现将“业务功能实现代码”和“错误处理代码”分离,提供更好的可读性。
使用try…catch捕获异常
Java异常处理机制的语法结构。
try {
// 业务实现代码
...
} catch (Exception e) {
// 错误处理代码
...
}
如果执行try块里的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给Java运行时环境,这个过程被称为抛出(throw)异常。
当Java运行时环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的catch块则把异常对象交给该catch块处理,这个过程称为捕获(catch)异常;如果Java运行时环境找不到捕获异常的catch块,则运行时环境终止,Java程序也将退出。
不管程序代码块是否处于try块中,甚至包括catch块中的代码,只要执行该代码块时出现了异常,系统总会自动生成一个异常对象。如果程序没有为这段代码定义任何的catch块,则Java运行时环境无法找到处理该异常的catch块,程序就在此退出。
异常类的继承体系
每个catch块都是专门用于处理该异常类及其子类的异常实例。
当Java运行时环境接收到异常对象后,会依次判断该异常对象是否是catch块后异常类或其子类的实例,如果是,Java运行时环境将调用该catch块来处理异常;否则再次拿该异常对象和下一个catch块里的异常类进行比较。
try块后的花括号({…})不可以省略,catch块后的花括号({…})也不可以省略。try块里声明的变量是代码块内局部变量,它只在try块内有效,在catch块中不能访问该变量。
Java把所有的非正常情况分为两种:异常(Exception)和错误(Error),它们都继承Throwable父类。
Error错误,一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,这种错误无法恢复或不可能捕获,这将导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不应该试图使用catch块来捕获Error对象。在定义该方法时,也无须在其throws子句中声明该方法可能抛出Error及其任何子类。
Java运行时的异常处理逻辑可能有如下几种情形。
- 数组越界异常,Java运行时将调用IndexOutOfBoundsException对应的catch块处理异常。
- 数字格式异常,Java运行时将调用NumberFormatException对应的catch块处理异常。
- 算术异常(例除0异常),Java运行时将调用AritheticException对应的catch块处理该异常。
- 如果程序运行时出现其他异常,该异常对象总是Exception类或其子类的实例,Java运行时将调用Exception对应的catch块处理该异常。
- 当试图调用一个null对象的实例方法或实例变量时,就会引发NullPointerException异常,Java运行将会调用对应的catch块来处理该异常。
总是把对应Exception类的catch块放在最后,实际上,进行异常捕获时不仅应该把Exception类对应的catch块放在最后,而且所有父类异常的catch块都应该排在子类异常catch块的后面,即先处理小异常,再处理大异常,否则将出现编译错误。
Java7新增的多异常捕获
在Java7以前,每个catch块只能捕获一种类型的异常;但从Java7开始,一个catch块可以捕获多种类型的异常。
使用一个catch块捕获多种类型的异常时需要注意如下两个地方。
- 捕获多种类型的异常时,多种异常类型之间用竖线(|)隔开。
- 捕获多种类型的异常时,异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值。
public class MultiExceptionTest {
public static void main(String[] args) {
try {
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
int c = a / b;
System.out.println("您输入的两个数相除的结果是: " + c);
} catch(IndexOutOfBoundsException | NumberFormatException | ArithmeticException ie) {
System.out.println("程序发生了数组越界、数字格式异常、算术异常之一");
// 捕获多异常时,异常变量默认有final修饰
// 所以下面代码有错
ie = new ArithmeticException("test");
}
}
}
访问异常信息
如果程序需要在catch块中访问异常对象的相关信息,则可以通过catch块的后异常形参来获得。当Java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋给catch块后的异常参数,程序即可通过该参数来获得异常的相关信息。
所有的异常对象都包含了如下几个常用方法。
getMessage()
:返回该异常的详细描述字符串。printStackTrace()
:将该异常的跟踪栈信息输出到标准错误输出。printStackTrace(PrintStream s)
:将该异常的跟踪栈信息输出到指定输出流。getStackTrace()
:返回该异常的跟踪栈信息。
public class AccessExceptionMsg {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("a.txt");
} catch (IOException ioe) {
System.out.println(ioe.getMessage());
ioe.printStackTrace();
}
}