Java 的异常处理机制
异常的概念
异常从字面上理解就是一种意外的情况,对于程序而言则是出现了错误,比如执行到某一句无法进行下去了。但是这样说并不是很准确,从更高的层面来看,其实异常的字面含义就是它的设计思想:一切不正常的状态都可以归为异常,不一定是程序代码或逻辑的错误,也可以是人为定义的错误状态,当然这涉及到具体处理事务的逻辑。在 Java
中异常是一种错误报告机制,我们可以定义异常,亦可以决定如何报告。
程序如何处理错误
在一些早期的语言中例如 C
,使用约定的方式来处理错误。通常由程序员自行定义错误,然后自行处理。非常典型的例子是通过返回状态码来确定执行状态。虽然也可以奏效,但是存在如下几个问题:
- 没有统一的规范。
- 非强制性。
- 错误处理代码和正常代码紧密耦合。
- 程序员需要考虑太多错误处理情况。
在产生问题后,通常我们就打开 debug
工具去查找问题,因为通过这些状态码实在看不出来问题出在哪里。
于是语言设计者们考虑到要将错误处理的方式统一到语言特性中去,强制所有的人使用一种错误处理机制,以增加程序的鲁棒性。Java
并不是第一个采用异常机制的语言,它从其他的语言中借鉴了许多。
终止模型与恢复模型
异常处理理论上有两种基本的模型。Java 支持终止模型。在这种模型中,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已无法挽回。
另一种称为恢复模型。意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。恢复模型希望异常被处理后能继续执行程序。比如操作系统通常都支持恢复模型,当程序崩溃时会试图恢复程序的运行。
Java 的异常体系
从这张图中可以看出所有的错误信息是从一个叫做 Throwable
的类继承而来,然后分为 Error
和 Exception
。Error
是不用程序员去关心的就像它的名字一样,代表着系统层面的错误,交给 JVM 去处理就好了,我们需要关心的就是 Exception
,以及它的各个子类。值得注意的是在 Exception
子类中有一类异常叫做 RuntimeExcetion
(运行时异常)。在 Java 中这类异常通常被称作未检查异常,意思是编译器不会强制你对它进行处理。而其他从 Exception
派生的异常则必须对其进行处理,否则无法编译通过。
使用异常处理错误
在 Java
中我们使用这样的语法捕捉异常:
try{
//可能产生异常的代码块
...
}catch(MyException e){
//捕获异常后的动作
...
}finally{
//无论是否捕获异常这里都会执行,通常做一些清理资源的操作
...
}
finally
是可选的。
也可以有多个 catch
:
try{
//可能产生异常的代码块
...
}catch(MyException e){
//捕获异常后的动作
...
}catch(Exception e){
...
}finally{
//无论是否捕获异常这里都会执行,通常做一些清理资源的操作
...
}
此时需要依照异常的关系(通常是继承关系)把 catch
子句排序,最终处理异常的子句是按顺序匹配的,一旦前一个匹配,后面的都不会执行。
或者没有 catch
但是必须有 finally
:
try{
//可能产生异常的代码块
...
}finally{
//无论是否捕获异常这里都会执行,通常做一些清理资源的操作
...
}
此时需要将异常抛出 throws
下面会讲解它的用法。这种语法逻辑的意义在于,抛出异常的方法总是将清理的工作完成了。接受异常的方法则只需要关心异常的处理,实现了 职责分离
。
自定义异常
我们可以通过继承 Exception
来实现自己的异常,就像这样:
public class MyException extends Exception {
//你也可以不提供这样的带参构造器,因为一般通过名字就可以知道是什么异常
public MyException(String msg) {
super(msg);
}
}
然后使用它:
public static void main(String[]args){
try{
throw new MyException("MyException");
}catch (MyException e){
//打印方法调用栈
e.printStackTrace(System.out);
}
控制台输出:
com.xiaqiguo.impl.MyException: MyException
at com.xiaqiguo.impl.TestException.main(TestException.java:9)
当然也可以在异常中记录一些信息,或者把异常信息输出到日志系统中。在实际应用中,异常是给大家使用的,所以记录日志的动作一般放在抛出异常的层面。
异常声明
当我们不想在此处处理异常时就需要把它交给上级处理,叫做抛出 throws
,由于这些动作写在方法上,更像是一种声明。例如:void f() throws MyException1,MyException2{...}
如果没有 throws 声明,则此方法不会抛出除 RuntimeException
外的其他异常。
子类覆盖父类
子类覆盖父类时只能抛出父类方法中异常声明里包含的异常。
总结
在了解了异常使用的基本方法和原则后,接下来需要注意一些陷阱,在下一篇中会详细的介绍一些技巧。着重关注 异常链
以及 异常声明
,最后了解下 finally
的注意事项。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于