目录
异常,是程序在运行期间发生的意外状况。对于程序设计者需要尽可能地预见可能发生的意外,尽可能地保证程序在各种糟糕的情况下仍可以运行。Java语言提供了成熟的异常处理机制。
Java异常体系
Java将所有的错误封装成为一个对象,Throwable类是这个异常体系的根,它有两个子类:Error和Exception。
Error
Error类及其子类对象代表了程序运行时Java系统内部的错误。对于Error,程序设计者无能为力,程序不能从Error恢复,因此不必处理它们,从技术上讲Error不是异常。
Exception
- Exception通常是由于某个资源不可用,或者正确执行程序所需的条件不满足所造成的。Exception类及其子类对象是程序设计者应该关心、并尽可能加以处理的部分。
Java将Exception分为两类:
- RuntimeException(运行时异常,也称为未检查异常Unchecked Exception)
- 非RuntimeException(也称为已检查异常 checked Exception)
以下称未检查异常和已检查异常
未检查异常
- 未检查异常包含java.lang.RuntimeException类及其所有子类
- 未检查异常是程序员没有进行必要的检查,因疏忽或者是错误而引起的异常,是可以避免的
已检查异常
- 除了java.lang.RuntimeException之外的所有异常都属于此类
- 最常见的如IOException及其子类(像找不到文件的FileNotFoundException,意外到达文件尾部EOFException等都是它的子类)、数据库访问错误SQLException、解析时出现的异常ParseException等,已检查异常是不可避免的
异常的捕获与处理
try语句块
- 将可能产生异常的代码放在try语句块中尝试执行,异常发生之后的代码不会被执行
- 在程序执行过程中,该段代码可能会产生并抛出一种或几种异常,这些异常都由它后面的catch负责捕获、处理。所以一个try语句块后面可以跟多个catch语句块(从语法的角度也可以一个catch都没有)
catch语句块
- 每个catch语句块捕获、处理一种类型的异常。当异常发生时,程序会中断正常的流程,离开try语句块去执行相应的catch语句块。
- 在catch中声明了异常对象(如ParseException e),异常对象封装了异常事件的相关信息,在catch语句块中可以使用这个对象获取这些信息,常用方法包括:
- getMessage():返回该异常的详细描述字符串。
- printStackTrace():将异常事件的跟踪栈信息输出。
注意:一般情况下,catch语句块只能捕获一种异常,但是Java 7 SE之后,允许catch捕获多个Exception,即当出现多个异常处理措施时,可以使用 | 符号隔开跟在catch后面。但是不允许这些异常出现继承关系。
finally语句块
- finally语句块为可选,一旦出现,无论try语句块是否抛出异常,finally代码块都要被执行
- finally语句块为异常处理提供统一的善后处理,使流程转到其他部分之前,能够对程序的状态进行统一的管理
- 通常在finally语句块中进行资源释放的工作,如关闭已打开的文件、关闭数据库连接等
try-catch-finally语句的执行过程
基本执行流程
-
try块中有return语句:
-
先执行try块中的代码,直到遇到return语句
-
计算return表达式的值(此时值会被暂存,但不会立即返回)
-
执行finally块中的代码
-
最后返回之前暂存的return值
-
-
catch块中有return语句:
-
如果try块抛出异常,进入匹配的catch块
-
执行catch块中的代码,直到遇到return语句
-
计算return表达式的值(暂存)
-
执行finally块中的代码
-
返回暂存的return值
-
实例:
try中有return,finally无return
public static int test1() {
try {
System.out.println("try block");
return 1; // 这个值会被暂存
} catch (Exception e) {
System.out.println("catch block");
return 2;
} finally {
System.out.println("finally block");
}
}
// 输出:
// try block
// finally block
// 返回: 1
try和finally都有return
public static int test2() {
try {
System.out.println("try block");
return 1; // 这个值会被丢弃
} catch (Exception e) {
System.out.println("catch block");
return 2;
} finally {
System.out.println("finally block");
return 3; // 这个值会覆盖之前的return
}
}
// 输出:
// try block
// finally block
// 返回: 3 (finally中的return会覆盖try中的return)
catch中有return
public static int test3() {
try {
System.out.println("try block");
throw new Exception();
} catch (Exception e) {
System.out.println("catch block");
return 2; // 这个值会被暂存
} finally {
System.out.println("finally block");
}
}
// 输出:
// try block
// catch block
// finally block
// 返回: 2
关键规则总结
-
finally优先执行:无论try或catch是否有return,finally都会执行(因为try、catch中是暂存的)
-
返回值确定时机:
-
如果finally中没有return:try或catch中的return值会在执行前计算并暂存,finally执行后返回
-
如果finally中有return:会覆盖之前try或catch中的return值
-
-
特殊情况:
-
如果在finally中修改了try/catch中要返回的变量,不会影响已暂存的基本类型值
-
但对于引用类型,修改对象状态会影响返回的对象
-
使用throws抛出异常
基本规则
[访问修饰符] 返回类型 方法名(参数列表) throws 异常类型1, 异常类型2, ... {
// 方法体
}
子类重写父类方法是throws的规则
- 可以抛出与父类相同的异常
- 可以抛出父类异常的子类
- 可以不抛出异常
经典练习:下面那个语句不能通过编译?
public class CC {
void doStuff() throws IOException{}
}
public class CC2 extends CC{
void doStuff() throws FileNotFoundException {}
}
public class CC3 extends CC{
void doStuff() throws Exception{}
}
public class CC4 extends CC{
void doStuff(int x) throws Exception{}
}
public class CC5 extends CC{
void doStuff(){}
}
自定义异常类
- 为该异常类取一个能标识异常状况的有意义的名字。
- 令其继承Exception类。
- 在异常类中至少定义两个构造方法:一个是无参的;另一个是带String参数的,将此字符串传递给父类Exception的相同构造方法。这个String将作为该异常对象的描述信息(即getMessage()方法的返回值)。