Java 异常处理
类的结构如下所示
[TOC]
Throwable 类
- Throwable 类是 Java 语言中所有错误和异常的超类,只有当一个对象直接或者间接的是此类的实例时,Java 才能通过 throw 语句抛出异常,同样,只有这种类才能被 try……catch 语句捕获进行处理
- Throwable 类的子类 Error 是错误,不是程序可以处理的,一般会是内存不足,线程终止,Java 虚拟机运行错误等,只能听之任之,JVM 通常会 kill 掉这个进程
Throwable 类的子类 Exception 是异常,是程序可以处理的,进一步分为 CheckedException 和 UncheckedException
- CheckedException 发生在编译阶段,代码语句必须使用 try……catch 或者 throws,否则无法进行编译,比如 StreamTokenizer 必须声明抛出异常 IOException,除了 Runtime Exception 类集之外,其他的 Exception 类集都是 CheckeException
- UncheckedException 发生在程序的运行阶段,一般来说具有不确定性,不容易定位和 DEBUG
Exception 类
该类指出了合理的应用程序想要捕获的异常条件,有两个参数,默认构造可以是两个可选参数,一个是详细信息,一个是异常原因,分别对应下述四个构造方法,分别于 super 对应
- public Exception()
- public Exception(String message)
- public Exception(String message, Throwable cause)
- public Exception(Throwable cause)
RuntimeException 类
- RuntimeException 是那些在 Java 虚拟机正常运行期间抛出的异常的超类,属于 uncheckedException,比如说发生数组越界,空指针等逻辑引起的问题
- 可能在执行方法期间抛出,但是没有被捕获的 RuntimeException 的任何子类都不用在 throws 子句中声明
包含四个构造方法
- RuntimeException()
- RuntimeException(String message)
- RuntimeException(String message, Throwable cause)
- RuntimeException(Throwable cause)
try - catch - finally
- 在 try 代码片段中,包含着有可能抛出异常的语句,如果无异常,则直接跳到 finally 代码块,否则相应的捕获异常,执行 catch 代码块的内容
- 无论是否 try 代码块发生异常,finally 都会被执行,可以在这里进行一些接口的 close 操作等
- 如果代码中显式的声明了某一个异常类型,则异常处理机制不会显示这个异常是在哪里抛出的,如果是没有声明异常遭遇中断,异常处理会显示出处
- 每一个 catch 按照书写的先后依次匹配执行,一旦亦常被某一个 catch 捕获,则必不会被其他的 catch 捕获处理
当 try 代码块或者 catch 代码块中包括 return 语句的时候,finally 代码块将会在 return 之前被强制执行,finally 不被执行仅仅当下列条件被满足:
- 代码在 finally 代码块之前即发生错误
- 代码在 finally 代码块之前进行了 System.exit()
- 当前线程由于某种情况丢失
- 关机,或者各种奇葩的可能条件
throw 和 throws
- throws 是方法抛出异常的声明,表示一个预动作,说明下方方法可能会抛出异常,throws 不做其他处理,仅仅是上交异常到调用,方法中抛出的任何异常都必须有 throws 声明,除非是将交由运行时系统自动抛出的 RuntimeException
- throw 是抛出异常,是一个明确的动作,必须与 try……catch 或者 throws 一起使用,抛出的只能是 Throwable 的类集中的类,如果所有的方法都将异常往上层调用者抛,那么 JVM 最后的处理就是打印异常消息和堆栈信息
自定义异常类型
一般来说,套路如下:
- 定义一个类,继承自 Throwable 或者某一个子类
- 构造方法,或者直接使用 super 的构造方法
- throws 声明,在代码块的某处 throw 该异常类
- catch 到抛出的异常进行处理
异常链机制
- 当异常较少的时候,我们可以对每一个异常进行 try 和 catch, 但是如果出现多个异常,显然通过一个 Exception 处理所有异常的方式会增加维护代码 DEBUG 的困难度,所以我们需要将异常信息进行封装,然后捕获我们的异常封装类即可
- 异常的处理方式有两种,一种是 throws 抛出异常后交给上级去处理,另一种就是使用 try 和 catch 进行具体的修正处理,异常链的处理采用第一种方式,在 try ……catch 的 catch 代码块中不做任何处理,唯一的动作就是抛出封装好的异常信息,然后会有 throws 抛出该异常,递归的每一层都如此控制异常抛出,就形成了一条异常链
- 如果我们想要在捕获到一个异常后抛出另外一个异常,那么,我们可以调用 Throwable 类集的 getCause()方法,得到类中的 cause 参数,这个参数保存的就是原始存有的异常信息,也就是说 cause 是导致抛出该 throwable 的 throwable
- 保留有异常链的 catch 代码块中可以使用 Throwable 的 printStackTrace()方法来打印整个异常信息
- getCause() 返回由一个需要 Throwable 的构造方法提供的 cause,或者是在创建 Throwable 之后通过 initCause()来设定 cause,子类不必要重写,也不必重写 printStackTrace()
- printStackTrace()方法会将对象的堆栈跟踪输出到输出流,作为 System.err 的值
几个建议
- 尽量在 try 中不要包含太多的代码,缩小异常范围圈
- 如果有的接口即使有异常也照样要运行,那么应该使用 finally
- 缩小 catch 捕获的异常,尽量使 catch 捕获明确的单个异常
- 抛出异常的时候,尽量包含易读易懂的描述和名称
- 不在构造函数中抛出异常,不在 finally 代码块中处理返回
- 不要老抛出异常,会降低 Java 的效率和系统的性能