异常
Java异常是用来描述一段代码中发生的异常情况(也就是错误)的对象。当出现引起异常的情况时,就会创建用来表示异常的对象,并在引起错误的地方中抛出异常对象。方法可以选择自己处理异常,也可以继续传递异常。无论采用哪种方式,在某一点就会捕获并处理异常。异常可以由Java运行时系统生成,也可以通过代码手动生成。由Java抛出的异常与那些违反Java语言规则或Java执行环境约束的基础性错误有关。手动生成的异常通常用于向方法的调用者报告某些错误条件。
Java异常处理通过5个关键字进行管理:try、catch、throw、throws以及finally。在try代码块中分装可能发生异常的程序语句,对这些语句进行监视。如果在try代码块中发生异常,就会将异常抛出。代码可以(使用catch)捕获异常,并以某些理性方式对其进行处理。系统生成的异常由Java运行时系统自动抛出。为了手动抛出异常,需要使用throw关键字。从方法抛出的任何异常都必须通过一条throws子句进行指定。在try代码块结束之后必须执行的所有代码都需要放入finally代码块中。
从JDK7开始,异常系统添加了3个有趣且有用的特性。
当资源(例如文件)不在需要时能够自行释放
多重捕获
多重捕获特性允许通过相同的catch子句捕获两个或更多个异常。两个或更多个异常处理程序使用相同的序列并非不寻常,尽管它们针对不同的异常进行响应。现在不比逐个捕获所有异常,可以通过一条catch子句使用相同的代码处理所有异常。
为了使用多重捕获,在catch子句中使用或运算符(|)分割每个异常。每个多重捕获参数都隐式地被声明为final(如果愿意的话,也可以显示指定final,但这不是必须的)。因此,不能为它们赋予新值。
catch (ArithmeticException | ArrayIndexOutOfBoundsException e)
- 最后重新抛出(final rethrow)或更精确地重新抛出(more precise rethrow)
“更精确地重新抛出”异常特特性会对重新抛出的异常类型进行限制,只能重新抛出满足以下条件的经检查的异常:由关联的try代码块抛出,没有被前面的catch子句处理过,并且是参数的子类型或超类型。虽然这个功能不经常需要,但是限制确实提供了这一特性。为了强制使用“更精确地重新抛出”异常特性,catch参数必须被有效地或显示地声明为final,这意味着在catch代码块中不能为之赋值。
1 | try { |
异常类型
所有异常类型都是内置类Throwable的子类。因此Throwable位于异常类中的顶部。紧随Throwable之下的两个子类,它们将异常分为两个不同的分支。一个分支是Exception类,这个类既可以用于用户程序应当捕获的异常,也可以用于创建自定义异常类型的子类。Exception有一个重要子类,名为RuntionExption,对于编写的程序而已,这种类型的异常是自动定义的,包括除零和无效数组索引等情况。
另外一个分支是Error类,该类定义了在常规环境下不希望程序捕获的异常。Error类型的异常由Java运行时系统使用,以指示运行时环境本身出现了某些错误,例如堆栈溢出错误等。
未捕获的异常
没有被程序捕获的所有异常,最终都将由默认处理程序进行处理。默认处理程序会显示一个描述异常的字符串,输出异常发生点的堆栈踪迹并终止程序。
堆栈踪迹总是会显示错误的方法调用序列。
尽管对于调试而言,Java运行时系统提供的默认异常处理程序很有用,但是通常希望自己处理异常。自己处理异常由两个优点:第一,允许修复错误;第二,阻止程序自动终止。如果程序停止运行并且物理何时发生错误都输出堆栈踪迹,会让大多数用户感到困惑!
try和catch
try以及catch语句构成一个单元。catch子句的作用域被限制在由之前try语句指定的那些语句内。catch语句不能捕获由另外一条try语句抛出的异常,只能由另外的try与其组合的catch来捕获。
大部分设计良好的catch子句,都应当能够分辨出异常情况,然后继续执行,就好像错误根本没有发生一样。
1 | public static void TestTryCatch() |
throw
对于异常,程序可以使用throw语句显示地抛出。throw语句之后的执行流会立即停止,所有后续语句都不会执行。检测最近的try代码块,查看是否存在和异常类型相匹配的catch语句。如果没有,就检测下一条try语句,这个过程一直重复下去。如果最终都没有找到匹配的catch语句,那么默认的异常处理程序终止程序并输出堆栈踪迹。
1 | public static void TestThrow() |
throws
如果方法可能引发自身不进行处理的异常,就必须指明这种行为,以方便方法的调用者能够保卫它们自己以防备上述异常。可以通过在方法声明中提供throws子句来完成该任务。throws子句列出了方法可能抛出的异常类型。除了Error和RuntimeException及其子类型类型的异常之外,对于所有其他类型的异常都是必须的。方法可能抛出的所有其他异常都必须在throws子句中进行声明。如果没有这么做,就会产生编译时错误。
1 | public static void TestThrows() throws IllegalAccessException |
finally
当抛出异常后,方法中的执行流会采用一条非常突然、非线性的路径,这将改变方法的正常执行流。根据方法的编码方式,异常甚至可能使方法比预期时间更早地返回。对于某些方法,这可能是一个问题。例如,如果方法在开始时打开一个文件,并在结束时关闭这个文件,那么我们可能不希望关闭文件的代码绕过异常处理机制。关键字finally就是为了解决这种可能情况而设计的。
使用finally可以创建一个代码块,该代码块会在执行try/catch代码块之后,并在执行try/catch代码块后面的代码之前执行。不管是否有异常抛出,都会执行finally代码块。如果抛出异常,那么即使没有catch语句能匹配异常,finally代码块仍将执行。finally子句是可选的。
1 | public static void TestFinally() |
内置异常
- 未经检查异常
在Java语言中,在所有方法的throws列表中不需要包含的异常,这些异常被称为未经检查的异常,因为编译器不检查方法中是否处理或抛出这些异常。
异常 | 含义 |
---|---|
ArithmeticException | 算术错误,例如除零 |
ArrayIndexOutOfBoundsException | 数组索引越界 |
ArrayStoreException | 使用不兼容的类型为数组元素赋值 |
ClassCastException | 无效转换 |
EnumConstantNotPresentException | 试图使用未定义的枚举 |
IllegalArgumentException | 使用非法参数调用方法 |
IllegaMonitorStateException | 非法的监视操作,例如等待未锁定的线程 |
IllegalStateException | 环境或应用程序处于不正确的状态 |
IllegalThreadStateException | 请求的操作与当前线程状态不兼容 |
IndexOutOfBoundsException | 某些类型的索引越界 |
NegativeArraySizeException | 使用负数长度创建数组 |
NullPointerException | 非法使用空引用 |
NumberFormatException | 字符串到数值格式的无效转换 |
SecurityException | 试图违反安全性 |
StringIndexOutOfBounds | 试图在字符串边界之外进行索引 |
TypeNotPresentException | 类型未找到 |
UnsupportedOperationException | 遇到不支持的操作 |
- 经检查异常
如果方法可能产生异常中的某个异常,并且方法本身不进行处理那么必须在方法的throws列表中包含该异常,这些异常被称为经检查的异常。
异常 | 含义 |
---|---|
ClassNotFoundException | 类未找到 |
CloneNotSupportedException | 试图复制没有实现Cloneable接口的对象 |
IllegalAccessException | 对类的访问被拒绝 |
InstantiationException | 试图为抽象类或接口创建对象 |
InterruptedException | 一个线程被另一个线程中断 |
NoSuchFieldException | 请求的域变量不存在 |
NoSuchMethodException | 请求的方法不存在 |
ReflectiveOperationException | 与反射相关的异常的超类 |
自定义异常
自定义异常需要继承Exception。Exception类没有为自己定义任何方法,它继承了Throwable提供的方法。因此所有异常,包括我们创建的异常,都可以获得Throwable定义的方法,对于这些方法,我们可以选择重写。
构造 | 描述 |
---|---|
Exception() | 没描述的异常 |
Exception(String msg) | 带描述的异常 |
Throwable(Throwable causeExc) | 链式异常 |
Throwable(String msg, Throwable causeExc) | 链式异常 |
方法 | 描述 |
---|---|
final void addSuppressed(Throwable exc) | 将exc添加到与调用异常管理的被抑制的异常列表中,主要用于新的带资源的try语句 |
Throwable fillInStackTrace() | 返回一个包含完整堆栈踪迹的Throwable对象,可以重写抛出该对象 |
Throwable getCause() | 返回引起当前异常的异常。如果不存在引起当前异常的异常,就返回null |
String getLocalizedMessage() | 返回异常的本地化描述 |
String getMessage() | 返回异常的描述 |
StackTraceElement[] getStackTrace() | 返回一个包含堆栈踪迹的数数组,数组元素的类型为StackTraceElement,每次一个元素。堆栈顶部的方法是抛出异常之前调用的最后一个方法,在数组的第一个元素中可以找到该方法。通过StackTraceElement类,程序可以访问与踪迹中每个元素相关的信息,例如方法名 |
final Throwable[] getSuppressed() | 获取与调用异常关联的被抑制的异常,并返回一个包含结果的数组。被抑制的异常主要由新的带资源的try语句生成 |
Throwable initCause(Throwable causeExc) | 将causeExc与调用异常关联到一起,作为调用异常的原因,返回对异常的引用 |
void printStackTrace() | 显示堆栈踪迹 |
void printStackTrace(PrintStream stream) | 将堆栈踪迹发送到指定的流中 |
void printStackTrace(printWriter stream) | 将堆栈踪迹发送到指定的流中 |
void setStackTrace(StackTraceElement elements[]) | 将elements中传递的元素设置为堆栈踪迹。该方法用于特殊的应用程序,在常规情况下不使用 |
String toString() | 返回包含异常描述的String对象,当通过println()输出Throwable对象是调用该方法 |
1 | class MyException extends Exception |
链式异常
通过链式异常,可以为异常关联另一个异常。第二个异常描述第一个异常的原因(描述当前异常原因的异常,在后面通常称为“引发异常”或“背后异常”)。
为了使用链式异常,向Throwable类添加了两个构造函数和方法。
- Throwable(Throwable causeExc)
- Throwable(String msg, Throwable causeExc)
- Throwable getCause()
- Throwable initCause(Throwable causeExc)