传统面向过程的编程语言如C语言都是通过函数返回值来处理错误,这种方式有以下缺点:
- 代码可读性差,需要对每个函数调用的返回值进行错误判断,如果出错了需要添加错误处理的逻辑,这种错误处理方式使正常业务逻辑和错误逻辑混在一起,降低了代码的可读性。
- 错误修复困难,如果想要通过这种方式修复错误是很困难的,因为出错点可能完全不知道怎么修复错误。
- 错误处理不是必须的,没有强制性,函数使用者可以不判断函数的返回值,从而绕过错误处理,影响程序健壮性。
Java异常处理系统是一种错误处理系统,它解决了传统错误处理方式的缺点。在异常处理系统中错误用异常来表示,错误通过抛出异常返回给调用者,调用者通过异常捕获来处理错误。
抛出异常
当方法里面有错误发生时,通过抛出一个异常对象来向外层调用者传递异常,抛出异常的示例如下:
public void doSomthing() throws Exception{
throw new Exception("error msg");
}
与不抛异常的方法相比,抛出异常的方法在末尾多了throws关键字,用来声明方法可能会抛出的所有异常类型,如果一个方法可能抛出多种异常,异常间使用逗号分隔,本例中doSomthing只抛出Exception这一种异常。在错误发生位置使用throw关键字抛出一个Exception对象,抛出的异常必须是throws声明的类的对象或者子类对象。 Exception类将在后面说明。
异常处理
异常处理系统捕获方法抛出的各种异常,然后进行错误处理或错误恢复操作,下面是Java异常处理概貌代码:
try{
//正常业务逻辑代码
}catch(Exception e){
//错误处理代码
}finally {
//清理代码
}
异常处理包括三个部分try、catch、finally。try里面用来放置正常的业务逻辑代码即方法调用。catch里面用来放置异常处理代码,每个catch后面都申明了一个异常对象,表示catch可以处理的异常,每个try后面可以包含多个catch块,表示不同的异常类型有不同的处理流程。finally里面的代码总是会被执行,所以一般用来放置资源清理的代码。可以看到异常机制把正常的业务逻辑和错误处理逻辑分离开了,代码的可读性非常高,同时可以在catch块里面修复错误,让程序继续正常运行。
检查类型(checked type)异常和非检查类型 (unchecked type)异常
异常分为检查类型异常和非检查类型异常两种。检查和非检查是相对于编译器来说的,当一个方法抛出检查类型异常时,编译器在编译期间会检查方法的调用者是否处理了该异常,如果发现异常没有被处理则编译器报错。非检查类型表示编译器在编译期间不会检查异常是否被处理。相比于没有异常机制的编程语言,Java异常机制强制用户必须处理检查类型的异常,这种强制性提升了程序的健壮性。
异常
Java中的错误有三类:Error、Exception、RuntimeException。Exception属于检查类型异常,Error和RuntimeException属于非检查类型异常,它们的关系如下图所示:
可以看到,Error和Exception继承自Throwable, RuntimeException继承自Exception,只有继承自 Throwable的类才能被异常处理系统捕获。
Error表示比较严重的错误,这类错误发生以后将无法被恢复,应用程序不应该捕获这类错误,而是让程序异常终止,Error属于运行时错误,它是非检查类型异常,例如内存不足错误、系统奔溃错误等。
Exception和RuntimeException属于运行时错误,这类错误发生后可以被恢复,应用程序可以继续执行,Exception属于检查类型异常,RuntimeException属于非检查类型异常。
Throwable类封装了一些错误信息,有错误描述信息和调用堆栈信息等,调用堆栈信息会在Throwable 对象创建时自动填充,错误信息可以通过构造方法传入,如:
throw new Exception("error msg");
通过getMessage方法可以获取错误描述信息,使用printStackTrace方法可以打印堆栈调用信息。如果Throwable不足以描述出错信息,可以通过继承Exception类来自定义错误描述信息。
异常匹配
每个try块后面可以跟随多个catch块用来处理不同的异常,当一个异常抛出时决定使用哪一个catch来处理异常的过程称为异常匹配。
异常处理系统会按照catch的书写顺序来找出处理程序,找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。当一个异常抛出时,如果catch里面申明的类型和异常一致或者是其父类型时,异常系统认为找到了处理程序,匹配结束。
finally
finally块里面的代码总是会被执行,即使try块或者catch块调用了return,或者catch里面有新的异常产生,或者try块捕获的异常不能匹配到catch,继续向外层抛出异常时,finally一样会被执行。示例如下:
try{
return;
}catch (Exception e){
return;
}finally {
//总是会被执行
System.out.println("finally");
}
try{
//后面的catch无法处理此异常
throw new Error("Error");
}catch (Exception e){
//catch块产生异常
throw new Exception("Exception");
}finally {
//总是会被执行
System.out.println("finally");
}
坑
如果在finally块里面使用return会导致try或者catch里面的异常无法抛出,示例如下:
public class Test {
public void ExceptionTest() throws Exception{
try{
throw new Exception("Exception in try");
}catch (Exception e){
throw new Exception("Exception in catch");
}finally {
//return 会导致catch块里面的异常无法抛出
return;
}
}
public static void main(String[] args){
Test t = new Test();
try {
t.ExceptionTest();
}catch (Exception e){
//ExceptionTest的catch块里面产生的异常无法捕获
System.out.println(e.getMessage());
}
}
}
上例中main函数里面无法捕获到ExceptionTest方法的catch块里面抛出的异常,导致异常丢失了,所以finally块里面使用return时需要特别注意。
最后
异常处理机制有一个作用是当错误发生时可以恢复错误,但是在实际开发中的一些错误往往很难恢复,异常处理程序所能做的工作仅仅是记录错误信息及堆栈信息。
