Java 的异常处理机制

本贴最后更新于 2680 天前,其中的信息可能已经天翻地覆

Java 的异常处理机制

异常的概念

异常从字面上理解就是一种意外的情况,对于程序而言则是出现了错误,比如执行到某一句无法进行下去了。但是这样说并不是很准确,从更高的层面来看,其实异常的字面含义就是它的设计思想:一切不正常的状态都可以归为异常,不一定是程序代码或逻辑的错误,也可以是人为定义的错误状态,当然这涉及到具体处理事务的逻辑。在 Java 中异常是一种错误报告机制,我们可以定义异常,亦可以决定如何报告。

程序如何处理错误

在一些早期的语言中例如 C,使用约定的方式来处理错误。通常由程序员自行定义错误,然后自行处理。非常典型的例子是通过返回状态码来确定执行状态。虽然也可以奏效,但是存在如下几个问题:

  1. 没有统一的规范。
  2. 非强制性。
  3. 错误处理代码和正常代码紧密耦合。
  4. 程序员需要考虑太多错误处理情况。

在产生问题后,通常我们就打开 debug 工具去查找问题,因为通过这些状态码实在看不出来问题出在哪里。

于是语言设计者们考虑到要将错误处理的方式统一到语言特性中去,强制所有的人使用一种错误处理机制,以增加程序的鲁棒性。Java 并不是第一个采用异常机制的语言,它从其他的语言中借鉴了许多。

终止模型与恢复模型

异常处理理论上有两种基本的模型。Java 支持终止模型。在这种模型中,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已无法挽回。

另一种称为恢复模型。意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。恢复模型希望异常被处理后能继续执行程序。比如操作系统通常都支持恢复模型,当程序崩溃时会试图恢复程序的运行。

Java 的异常体系

Java 异常体系简图

从这张图中可以看出所有的错误信息是从一个叫做 Throwable 的类继承而来,然后分为 ErrorExceptionError 是不用程序员去关心的就像它的名字一样,代表着系统层面的错误,交给 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 的注意事项。

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3454 回帖 • 189 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...