异常机制

  • 概述
  • 常见异常
  • 异常处理
  • 其他问题
  • 参考

 

概述


如果使用异常,就不必在方法调用处进行检查,异常机制将保证能够捕获这个错误,并且,只需在一个地方处理错误,即所谓的异常处理程序中。这种方式不仅节约代码,而且把 “概述在正常执行过程中做什么事” 的代码和 “出了问题怎么办” 的代码相分离。异常机制使代码的阅读、编写和调试工作更加井井有条。

异常情形是指阻止当前方法或者作用域继续执行的问题。异常处理机制就是告诉你:这里可能会或者已经产生了错误,您的程序出现了不正常的情况,可能会导致程序失败!

只有在你当前的环境下程序无法正常运行下去,也就是说程序已经无法来正确解决问题了,这时它所就会从当前环境中跳出,并抛出异常。

抛出异常后,将会执行以下流程:首先,它会使用 new 创建一个异常对象,然后在产生异常的位置终止程序,并且从当前环境中弹出对异常对象的引用,这时。异常处理机制就会接管程序,并开始寻找一个恰当的地方来继续执行程序,这个恰当的地方就是异常处理程序。

总的来说异常处理机制就是当程序发生异常时,它强制终止程序运行,记录异常信息并将这些信息反馈给我们,由我们来确定是否处理异常。

 

常见异常


Throwable 是 Java 异常中最顶层的类,所有的异常类都继承于 Throwable。异常类分成两个部分,一个是 Error,另一个是 Exception,这两个类直接继承自 Throwable。

异常类的组织结构如下所示:

异常机制_第1张图片

图源自 一张图搞清楚Java异常机制。

Error 是非程序异常,即程序不能捕获的异常,一般是编译或者系统性的错误,如 OutOfMemory 内存溢出异常等。

Exception 是程序异常类,由程序内部产生。Exception又分为运行时异常、非运行时异常。

运行时异常 不需要显式处理。当出现这样的异常时,总是由虚拟机接管。比如 NullPointerException,IndexOutOfBoundsException 这两种最常见的异常,不需要程序员捕获和抛出。 出现运行时异常后,系统会把异常一直往上层抛。对于多线程而言,抛出异常的线程将会直接中止,对于主程序而言,程序直接退出。

非运行时异常 如 IOException(输入输出异常)、FileNotFoundException(文件没发现异常)、SQLException(SQL异常),是程序必须进行处理的异常,必须捕获然后抛出,如果不处理程序就不能编译通过。

 

异常处理


try-catch-finally

通常使用 try-catch-finally 语句块处理异常,或者在函数签名中使用 throws 声明交给函数调用者 caller 去解决(调用者也需要 try-catch 来捕获异常并处理)。

try 块中放可能发生异常的代码。如果 try 的执行过程中没有发生异常,则不会进入 catch,而是执行 finally 块和 finally 块之后的代码。如果发生异常,try 块中后面的代码不会再运行了,开始尝试匹配 catch 块。

catch 的作用是捕获异常,且可以同时包含多个 catch 块,即捕获多个异常。每一个catch 块用于捕获并处理一个异常。catch 需要定义异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个 catch 块来处理异常。如果当前 try 块中发生的异常在后续的所有 catch 中都没捕获到,则先去执行 finally,然后到这个函数的外部 caller 中去匹配异常处理器。

finally 无论异常是否发生或被捕获,finally 块中的内容都会执行(除非调用了 System.exit(0) 退出虚拟机)当然执行 finally 块的前提是已经进入 try-catch-finally 语句了。finally 主要做一些清理工作,如流的关闭、数据库连接的关闭、释放锁,释放资源等。


throws 与 throw

throws 是另外一种异常处理的方法,在函数头中,加上 throws Exception 来标明该成员函数可能抛出的各种异常。如果方法抛出了异常那么调用这个方法的时候就需要将这个异常处理。采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。

throw 是用来抛出异常的,可以抛出任意 Throwable (i.e. Throwable 或任何Throwable的衍生类)。

总的来说,throws 是一个声明,而 throw 是一个动作。

 

其他问题


finally 和 return

try/catch 块中包含 return 语句时,finally 块的语句在 try/catch 中的 return 语句执行之后但返回之前执行。如果 finally 语句中也有 return 语句,则覆盖 try/catch 中的 return 语句直接返回。

对于 finally 块中改变了 return 返回值的情况,由于 return 的时候是复制了一个变量然后返回,所以之后 finally 操作的变量如果是基本类型的话不会影响返回值。


自定义异常

自定义异常类需要扩展 Exception 类,这样的自定义异常类属于检查异常类(类似于 IOException)。如果要自定义非检查异常,则需要扩展 RuntimeException。

按照国际惯例,自定义的异常应该总是包含如下的构造函数:

  • 一个无参构造函数
  • 一个带有 String 参数的构造函数,并传递给父类的构造函数。
  • 一个带有 String 参数和 Throwable 参数,并都传递给父类构造函数
  • 一个带有 Throwable 参数的构造函数,并传递给父类的构造函数。

例如 IOException 定义如下:

    public class IOException extends Exception
    {
        static final long serialVersionUID = 7818375828146090155L;
        public IOException()
        {
            super();
        }
        public IOException(String message)
        {
            super(message);
        }
        public IOException(String message, Throwable cause)
        {
            super(message, cause);
        }
        public IOException(Throwable cause)
        {
            super(cause);
        }
    }

此部分完全引用自 “一文搞懂Java中的异常机制”。


异常链

假设B模块完成自己的逻辑需要调用A模块的方法,如果A模块发生异常,则B也将不能完成而发生异常。但是B在抛出异常时,会将A的异常信息掩盖掉,这将使得异常的根源信息丢失。所以需要异常链将抛出异常的模块联系起来。

此项工作主要有 Throwable 中的 cause 属性完成的,它保存了构造时传递的根源异常参数,源码中 cause 的注释如下所示:

cause 保存导致当前 Throwable 被抛出的 Throwable,如果此 Throwable 没有因为其他 Throwable 被抛出,或者抛出者未知,cause 的值为 null。如果 cause 指向自身,表示还没有被初始化。


final,finalize,finally

final 和 finally 是Java的关键字,而 finalize 是方法。
final 关键字用于创建不可变的类、方法、变量。
finally 关键字用于异常处理,通常用于 try-catch-finally 结构块。
finalize() 方法在垃圾回收时使用,详见 《深入理解 Java 虚拟机》阅读笔记 - 垃圾回收机制。


使用

  • Java 的异常执行流程是线程独立的,线程之间没有影响。上文提到过,如果当前程序是多线程的,没有被任何代码处理的异常仅仅会导致异常所在的线程结束。如果是单线程的,将会导致进程结束。线程的问题应该由线程自己来解决,而不要委托到外部,也不会直接影响到其它线程的执行。
  • 不要在 finally 中使用 return;不要在 finally 中抛出异常;减轻 finally 的任务,不要在 finally 中做一些其它的事情,finally 块仅仅用来释放资源是最合适的;将尽量将所有的 return 写在函数的最后面,而不是 try-catch-finally 中。

 

参考


  • 埃克尔著, 侯捷. Java编程思想, (第2版)[M]. 2002.
  • 一文搞懂Java中的异常机制
  • 一张图搞清楚Java异常机制
  • 运行时异常和检查性异常区别

你可能感兴趣的:(异常机制)