Java 异常处理机制

1. 通过异常处理错误

Java的基本理念是“结构不佳的代码不能运行”。

发现错误的理想时机是在编译阶段,即运行程序之前。然而,编译期间不能找出所有的错误,余下的问题就必须在运行期间解决。这就需要错误源能通过某种方式把适当的信息传递给接收者,以便接收者知道该如何正确处理这个问题。

解决的办法是,用强制规定的形式来消除错误处理过程中各种各样的因素。

问题出现了,可能在当前的环境中还没有足够的信息来解决这个问题,但是又不能对它置之不理,所以就把这个问题提交到一个更高级别的环境里,并在那里作出正确的决定。

使用异常的另一个好处是,降低错误处理代码的复杂度。如果不使用异常,那就必须检查特定的错误,并且在程序的每个地方都要对它进行处理;如果使用异常,就不用在方法调用处进行检查,因为异常处理机制能够捕获这个错误,并且只需在一个地方处理错误,即异常处理程序中。

Java 异常处理用到的几个关键字:try、catch、finally、throw、throws。

2. 捕获异常(try、catch 关键字)

2.1 try 块

将要被监听的代码(可能抛出异常的代码)放在 try 语句块(称为 try 块)之内,当 try 语句块内发生异常时,异常就被抛出。

2.2 异常处理程序

抛出的异常必须在某处得到处理,这个“地方”就是异常处理程序。异常处理程序紧跟在 try 块之后,以关键字 catch 表示:

try{
	// 可能存在异常的代码
}catch(Type1 id1){
	// 处理类型为 Type1 的异常
}catch(Type2 id2){
	// 处理类型为 Type2 的异常
}... ...

每个 catch 子句仅接收一个由参数指定的特殊类型的异常(Type1,Type2 等)。

异常处理程序必须紧跟在 try 块之后。 当异常被抛出时,异常处理机制就负责搜寻参数与异常类型相匹配的第一个异常处理程序。然后进入 catch 子句执行,异常就得到了处理。一旦 catch 子句结束,则处理程序的查找过程也就结束了。

注意:只有类型匹配的 catch 子句会被执行。

3. 抛出异常(throw 关键字)

异常情形(exceptional condition)是指阻止当前方法或作用域继续执行的问题。 要把异常情形和普通问题区分开,普通问题是指在当前环境能够得到足够的信息来处理这个错误;异常情形是指当前环境没有足够的信息能解决这个问题,所以只能从当前环境跳出,把问题提交给上一级环境,这就是抛出异常

throw 关键字就是用来抛出异常的。使用 try…catch… 捕获异常后,在 catch 语句中使用 throw 抛出异常,如下:

try{
	// 可能存在异常的代码
}catch(Exception e){
	throw new Exception();
}

当抛出异常后,当前的执行路径被终止(不能再继续执行)并从当前环境弹出对异常对象的引用。此时,异常处理机制接管程序,寻找异常处理程序。异常处理程序的任务就是将程序从错误状态恢复,使得程序要么换一种方式运行,要么继续执行下去。这也是处理异常的过程。

4. 异常说明(throws 关键字)

Java 提供了相应的语法(并强制使用这个语法),使得程序员能够告知客户端程序员某个方法可能会抛出的异常类型,然后客户端程序员就能进行相应的处理。这就是异常说明,它属于方法声明的一部分,紧跟在形式参数列表之后。

异常说明使用关键字 throws,后面接一个潜在异常类型的列表,方法声明如下:

void f() throws Exception{
	// ...
}

方法内的代码必须与异常说明一致。 如果方法里的代码产生了异常却没有进行处理,编译器会发现这个问题并提醒你:要么处理这个异常(在方法内使用 try…catch…处理异常),要么在异常说明中表明此方法将产生异常。

这种在编译期被强制检查的异常称为被检查的异常。

5. Java 标准异常

5.1 Java 异常类框架

在这里插入图片描述
(1)Throwable

Throwable 是 Java 中所有错误或异常的超类,分为两种类型: Error 和 Exception。Error 表示编译时和系统错误(一般不用关心),Exception 是可以被抛出的基本类型。

(2)Exception

Exception 及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。

(3)Error

Error 是 Java 程序运行中不可预料的异常情况,不能被用户捕获,JVM 也不能处理。比如 OutOfMemoryError、NoClassDefFoundError 等。

(4)RuntimeException

RuntimeException 是那些可能在运行期间抛出的异常的超类。

编译器不会检查 RuntimeException 异常。例如,除数为零时,抛出ArithmeticException异常。当代码发生除数为零的情况时,倘若既"没有通过 throws 声明抛出 ArithmeticException 异常",也"没有通过 try…catch…处理该异常",也能通过编译,但是在代码运行时就会出现异常。

(5)IOException

IOException 是所有输入、输出异常的超类。

5.2 Java 异常分类

Java 将可抛出(Throwable)的结构分为三种类型:被检查的异常(Checked Exception)、运行时异常(RuntimeException)和错误(Error)。

在抛出异常或错误时,到底该用哪一种?《Effective Java》中给出的建议是:对于可以恢复的条件使用被检查异常,对于程序错误使用运行时异常。

  • 检查性异常(编译时异常):必须在编写代码是使用 try-catch 捕获。比如 FileNotFoundException、ClassNotFoundException、NoSuchMethodException、NoSuchFieldException 等。继承自 java.lang.Exception 类。
  • 非检查性异常(运行时异常):在代码编写过程中,可以忽略捕获操作。比如 NullPointerException、ArithmeticException、ArrayIndexOutOfBoundsException 等。继承自 RuntimeException 类。

6. 创建自定义异常

可以自己定义异常类来表示程序中可能遇到的特定问题。要自己定义异常类,必须从已有的异常类继承。如果知道可能出现的异常的类型,最好是选择意思相近的异常类继承;如果不能确定,就选择继承 Exception 类。

创建一个简单异常类进行测试:

package exceptionTest;
// 创建一个简单异常类,继承Exception类
// SimpleException类中没有创建构造器,抛出异常时调用父类的默认构造器
// 也可以自己创建带参数的构造器
class SimpleException extends Exception{ }

public class ExceptionTest {
	// f()方法抛出简单异常
	public void f() throws SimpleException{
		System.out.println("Throw exception from f().");
		throw new SimpleException();
	}
	
	public static void main(String[] args) {
		ExceptionTest et = new ExceptionTest();
		try{
			et.f();
		}catch(SimpleException e){
			System.out.println("Caught it!");
		}
	}

}

测试结果:

Throw exception from f().
Caught it!

7. 使用 finally 进行清理

对于一些代码,可能会希望无论 try 块中的异常是否抛出,它们都能得到执行。这通常适用于内存回收之外的情况(回收由垃圾回收器完成)。为了达到这个效果,可以在异常处理后面加上 finally 子句,就得到了完整的异常处理程序:

try{
	// 可能存在异常的代码
}catch(Type1 id1){
	// 处理类型为 Type1 的异常
}catch(Type2 id2){
	// 处理类型为 Type2 的异常
}finally{
	// 总要执行的语句,用于清理资源
}

无论异常是否抛出,finally 子句总能被执行,所以使用 finally 来清理资源。

对于没有垃圾回收机制的语言来说,finally 非常重要。它能使程序保证:无论 try 块里发生了什么,内存总能得到释放。Java 有垃圾回收机制,所以内存释放不是问题。但是要把除内存之外的资源恢复到它们的初始状态时,就要用到 finally 语句。这种需要清理的资源包括:已经打开的文件或网络连接,在屏幕上画的图形等等。

创建一个开关类进行测试:

package exceptionTest;
// 创建一个开关类
class Switch{
	private boolean state = false;
	
	public void on(){
		state = true;
		System.out.println(this);
	}
	
	public void off(){
		state = false;
		System.out.println(this);
	}
	
	public String toString(){
		return state ? "on" : "off";
	}
}

// 创建两个开关异常类
class SwitchException1 extends Exception{}

class SwitchException2 extends Exception{}

public class SwitchTest {
	private static Switch s = new Switch();
	
	public static void f() throws SwitchException1, SwitchException2{ }
	
	public static void main(String[] args) {
		try{
			s.on();
			f();
			s.off();
		}catch(SwitchException1 e1){
			System.out.println("SwitchException1 e1");
			s.off();
		}catch(SwitchException2 e2){
			System.out.println("SwitchException2 e2");
			s.off();
		}
	}

}

在每个 try 块和异常处理程序的末尾都有对 s.off() 方法的调用,目的是确保 main() 结束的时候,开关是关闭的。但也有可能出现这种情况:异常被抛出,但没有被捕获,这时 s.off() 就得不到调用。有了 finally,把 try 块中的清理语句放到 finally 中即可,如下:

public static void main(String[] args) {
		try{
			s.on();
			f();
		}catch(SwitchException1 e1){
			System.out.println("SwitchException1 e1");
		}catch(SwitchException2 e2){
			System.out.println("SwitchException2 e2");
		}finally{
			s.off();
		}
}

这样就能保证在任何情况下,s.off() 都能执行。

当涉及 break、continue 和 return 的时候,finally 子句也会得到执行。 看下面这个例题(来自牛客网)。

以下代码执行后输出结果为( )

public class Test {
    public static void main(String[] args) {
        System.out.println("return value of getValue(): " + getValue());
    }
     public static int getValue() {
         try {
             return 0;
         } finally {
             return 1;
         }
     }
 }

A. return value of getValue(): 1

B. return value of getValue(): 0

C. return value of getValue(): 0return value of getValue(): 1

D. return value of getValue(): 1return value of getValue(): 0

答案: A

解析:

根据官方的JVM规范:

如果 try 语句里有 return,返回的是 try 语句块中的变量值。

详细执行过程如下:

  • 如果有返回值,就把返回值保存到局部变量中;
  • 执行 jsr 指令跳到 finally 语句里执行;
  • 执行完 finally 语句后,返回之前保存在局部变量表里的值。

如果 try,finally 语句里均有 return,忽略 try 的 return,而使用 finally 的 return。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值