当Java运行时系统接收到异常对象时,会寻找能处理这一异常的代码,并把当前异常对象交给其处理,这一过程成为捕获(catch)异常。如果Java运行时系统找不到可以捕获异常的代码,则运行时系统将终止,相应的Java程序也将推出。最终Java运行时系统将异常的相关信息输出(显示到System.err中)。
如何捕获异常呢?在Java中,捕获异常需要使用catch关键字,而后紧跟catch关键字的语句块就是异常处理代码。捕获异常犹如狩猎,无法发现猎物就谈不上捕获,所以在catch语句块的前面需要一个try语句来监听异常的抛出。try语句块就是try关键字跟随的语句块,其中是可能引发异常的代码。如下所示:
try{
// 一些可能抛出异常的代码
}catch(TypeException1 e1){
// 处理TypeException1类型异常的代码
}catch(TypeException2 e2){
// 处理TypeException2类型异常的代码
}
上面代码演示了Java中异常处理的模式:先监听(try)后捕获(catch)。至于异常本身,它也是一个对象,有它自己的类型,所以我们的catch需要指定一个异常的类型。当程序在运行时期间出现一个错误时,Java运行时系统会在对象堆中创建一个异常类型的对象,并在指定的catch语句块中赋给匹配的异常类型的变量(上面代码中的e1和e2)。
完整的异常处理的语法结构是如下的形式:
try{
//可能发生的异常代码块
}catch(TypeException1 e1){
// 处理TypeException1类型的异常的代码
// e1是TypeException1类型的异常对象
}catch(TypeException2 e2){
// 处理TypeException2类型的异常的代码
// e2是TypeException2类型的异常对象
...
}finally{
// 此处代码块作为异常处理完成的收尾工作的代码块。
// 请注意:不管try中的代码块正常执行还是抛出了异常,finally语句块中的代码都会被执行。
}
看如下的一段代码,来理解Java的异常处理。
package com.test;
public class ExcepTest {
public int divide(int a,int b) {
int c = a / b;
return c;
}
public static void main(String[] args) {
ExcepTest et = new ExcepTest();
try{
et.divide(5,0);
}catch (ArithmeticException ae){
System.out.println("除数不能为0");
}catch (Exception e){
System.out.println(e.toString()); // toString()方法输出异常对象的简短描述
}finally {
System.out.println("清理资源。");
}
}
}
(1)所有异常的基类都是Throwable,在这个类中有三个常用的方法,用于输出异常的相关信息,如下所示:
- public String getMessage() //返回异常对象的详细信息
- public String toString() //返回异常对象的简短描述
- public void printStackTrace() // 把异常对象的栈跟踪信息打印到标准的错误流(System.err对象)中。
修改上面处理ArithmeticException类型的异常代码块,可以如下:
package com.test;
public class ExcepTest {
public int divide(int a,int b) {
int c = a / b;
return c;
}
public static void main(String[] args) {
ExcepTest et = new ExcepTest();
try{
et.divide(5,0);
}catch (ArithmeticException ae){
System.err.println("getMessage:" + ae.getMessage());
System.out.println("--------------------------");
System.err.println("toString: " + ae.toString());
System.out.println("--------------------------");
// 异常对象的printStackTrace()本身就是输出异常的栈跟踪信息,因此不需要,也不能调用System.err.println()
ae.printStackTrace();
}catch (Exception e){
System.out.println(e.toString()); // toString()方法输出异常对象的简短描述
}finally {
System.out.println("清理资源。");
}
}
}
(2)使用finally进行清理:在程序中经常会访问一些外部资源,如文件、数据库或者网络等,在资源访问结束后,我们会关闭或者释放资源。如果一切正常,代码顺序执行,那么资源释放的代码也会被执行。然而一旦发生异常,结果就变得不可预料了。
finally关键字后面的代码块将解决上面的问题。不管try语句块中代码是否抛出异常,finally语句块中的代码都将会被执行,因此比较适合在finally语句块中编写释放资源的代码。比如上面代码,发生“除数不能为0”的异常,捕获此类型的异常处理代码块中并没有输出“除数不能为0”信息的代码,但是finally语句块中有代码执行。
(3)在异常处理代码中定义了finally语句块,在捕获catch语句中即使有return语句,在执行该语句之前,Java运行时系统也会保证finally语句块中的代码被执行。
public class ExcepTest {
public int divide(int a,int b) {
int c = a / b;
return c;
}
public static void main(String[] args) {
ExcepTest et = new ExcepTest();
try{
et.divide(5,1);
//
return;
}catch (ArithmeticException ae){
System.err.println("getMessage:" + ae.getMessage());
System.out.println("--------------------------");
System.err.println("toString: " + ae.toString());
System.out.println("--------------------------");
// 异常对象的printStackTrace()本身就是输出异常的栈跟踪信息,因此不需要,也不能调用System.err.println()
ae.printStackTrace();
}catch (Exception e){
System.out.println(e.toString()); // toString()方法输出异常对象的简短描述
}finally {
System.out.println("清理资源。");
}
}
}
上面代码,在try语句中执行et.divide(5,1),除数为1,代码正确执行,然后跟了一个return语句,但是运行上面代码依然会输出:"清理资源。",说明finally语句块依然被执行了。
(4)Exception类是所有异常类的基类,因此不管抛出何种异常,catch(Exception e)都能进行捕获,异常捕获机制是,只要发现了有匹配的就不会再执行后面的catch语句。因此,如果程序中把catch(Exception e)语句块放在紧挨try语句块,而在另外的处理其它异常的catch语句之前,Java编译器将提示错误。
(5)try语句并不一定要接catch语句,也可以只和finally语句一起使用,这通常用于在对异常的信息不感兴趣,也不需要对异常做进一步的处理的情况下,但是又需要做一些收尾的工作,以保证资源的合理释放等。
(6)finally语句并不是在所有情况下都会被调用执行,当程序中调用了System.exit(1)直接终止当前运行的Java虚拟机时,finally语句并不会被调用。
package com.test;
public class ExcepTest {
public int divide(int a,int b) {
int c = a / b;
return c;
}
public static void main(String[] args) {
ExcepTest et = new ExcepTest();
try{
et.divide(5,1);
// 退出系统
System.exit(1);
}catch (ArithmeticException ae){
System.err.println("getMessage:" + ae.getMessage());
System.out.println("--------------------------");
System.err.println("toString: " + ae.toString());
System.out.println("--------------------------");
// 异常对象的printStackTrace()本身就是输出异常的栈跟踪信息,因此不需要,也不能调用System.err.println()
ae.printStackTrace();
}catch (Exception e){
System.out.println(e.toString()); // toString()方法输出异常对象的简短描述
}finally {
System.out.println("清理资源。");
}
}
}
运行上面代码,将不会显示任何信息,也就是finally语句块代码没有被执行。
(7)在finally语句块中执行的代码也有可能发生新的异常,可以在finally语句代码块中继续try...catch进行监听及捕获处理异常。