在程序执行过程中,可能有各种出错的情况,有的是不可控的内部原因,比如内存不够了、磁盘满了,有的是不可控的外部原因,比如网络连接有问题,更多的可能是程序的编写错误,比如引用变量未初始化就直接调用实例方法。这些非正常情况在Java中都被认为是异常。
异常简介
异常是相对于 return 的一种退出机制,可以由系统触发,也可以由程序通过 throw 语句触发,异常可以通过 try/catch 语句进行捕获并处理,如果没有捕获,则会导致程序退出并输出异常栈信息。
当发生异常时,Java 会启用异常处理机制,首先创建一个异常对象,然后异常处理机制会从当前方法开始查找看谁 “捕获” 了这个异常,当前方法没有就查看上一层,直到 main 方法,如果也没有,就使用默认机制,输出异常栈信息并退出。
异常栈信息包括从异常发生点到最上层调用者的轨迹,还包括行号,是分析异常最为重要的信息。
对于异常栈信息,普通用户无法理解,也不知道该怎么办,我们需要给用户一个更为友好的信息,又或者我们不希望程序在出现异常后就退出程序,而是抛出异常然后继续执行,为此需要使用 try/catch 捕获异常。
try 后面的花括号 {}
内包含可能抛出异常的代码,catch 语句包含能捕获的异常和处理代码,捕获异常后,程序就不会异常退出了,但 try 语句内异常点之后的代码不会执行,执行完 catch 内的语句后,程序会继续执行 try/catch 之后的代码。
Throwable
java.lang.Throwable 是所有异常的基类,它有两个子类:Error 和 Exception。
Error 用来指示运行时环境发生的错误,一般发生在严重故障时,Java 程序通常不捕获错误,比如 内存溢出错误(OutOfMemory-Error)和栈溢出错误(StackOverflowError)。
Exception 表示应用程序错误,它有很多子类,应用程序也可以通过继承 Exception 或其子类创建自定义异常。
RuntimeException 是未受检异常(unchecked exception),IOException、SQLException 和 Exception 自身则是受检异常(checked exception)。对于受检异常,Java 会强制要求程序员进行处理,否则会有编译错误,而对于未受检异常则没有这个要求。
大部分类在继承父类后只是定义了几个构造方法,这些构造方法也只是调用了父类的构造方法,并没有额外的操作。定义这么多不同的异常类主要是为了名字不同,异常类的名字本身就代表了异常的关键信息。
自定义异常
一般是继承 Exception 或者它的某个子类。如果父类是 RuntimeException 或它的某个子类,则自定义异常也是未受检异常;如果是 Exception 或 RuntimeException 以外的其他子类,则自定义异常是受检异常。
// 自定义异常类,继承Exception类
public class InsufficientFundsException extends Exception {
// 此处的amount用来储存当出现异常(取出钱多于余额时)所缺乏的钱
private double amount;
public InsufficientFundsException(double amount) {
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
异常处理
try catch
使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方,try/catch 中的代码称为保护代码。
// 自定义异常类,继承Exception类
public class InsufficientFundsException extends Exception {
// 此处的amount用来储存当出现异常(取出钱多于余额时)所缺乏的钱
private double amount;
public InsufficientFundsException(double amount) {
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获。
try {
file = new FileInputStream(fileName);
x = (byte) file.read();
} catch(FileNotFoundException f) { // Not valid!
f.printStackTrace();
return -1;
} catch(IOException i) {
i.printStackTrace();
return -1;
}
throw
在 catch 块内处理完后,可以重新抛出异常,异常可以是原来的,也可以是新建的。
如果一个方法抛出检查性异常,那么该方法必须使用 throws 关键字来声明抛出的异常,否则不能抛出。
throws 跟在方法的括号后面,可以声明多个异常,以逗号分隔。
声明的含义是,这个方法内可能抛出这些异常,且没有没有处理完这些异常,调用者必须进行处理。
public void test() throws Exception {
try{
// 可能触发异常的代码
}catch(NumberFormatException e){
System.out.println("not valid number");
throw new AppException("输入格式不正确", e);
}catch(Exception e){
e.printStackTrace();
throw e;
}
}
finally
finally 内的代码不管有无异常发生,都会执行。一般用于释放资源,如数据库连接、文件流等。
- 如果没有异常发生,在 try 内的代码执行结束后执行
- 如果有异常发生且被 catch 捕获,在 catch 内的代码执行结束后执行
- 如果有异常发生但没被 catch 捕获,则在异常 throw 给上层之前执行
- 如果在 try 或者 catch 语句内有 return 语句,则 return 语句在 finally 语句执行结束后才执行,对于基本数据类型,finally 中并不能改变返回值(可以理解为返回值存储在返回值存储器中,finally 的操作不会改变返回值存储器中的值),对于对象类型,可以改变其中的值(返回值存储器存储的是对象地址)。
- 如果 finally 中有 return,不仅会覆盖 try 和 catch 内的返回值,还会掩盖 try 和 catch 内的异常,就像异常没有发生一样,应该避免在 finally 中使用 return 语句或者抛出异常
try {
// 可能抛出异常
} catch (Exception e){
// 捕获异常
} finally {
// 不管有无异常都执行
}