java-doc-exception

异常

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
2
3
4
5
6
7
8
9
10
try {
// ...
} catch(ExceptionType1 exOb) {
// ...
} catch(ExceptionType2 exOb) {
// ...
} // ...
finally {
// ...
}

异常类型

所有异常类型都是内置类Throwable的子类。因此Throwable位于异常类中的顶部。紧随Throwable之下的两个子类,它们将异常分为两个不同的分支。一个分支是Exception类,这个类既可以用于用户程序应当捕获的异常,也可以用于创建自定义异常类型的子类。Exception有一个重要子类,名为RuntionExption,对于编写的程序而已,这种类型的异常是自动定义的,包括除零和无效数组索引等情况。

另外一个分支是Error类,该类定义了在常规环境下不希望程序捕获的异常。Error类型的异常由Java运行时系统使用,以指示运行时环境本身出现了某些错误,例如堆栈溢出错误等。

顶级的异常层次

顶级的异常层次

未捕获的异常

没有被程序捕获的所有异常,最终都将由默认处理程序进行处理。默认处理程序会显示一个描述异常的字符串,输出异常发生点的堆栈踪迹并终止程序。

堆栈踪迹总是会显示错误的方法调用序列。

尽管对于调试而言,Java运行时系统提供的默认异常处理程序很有用,但是通常希望自己处理异常。自己处理异常由两个优点:第一,允许修复错误;第二,阻止程序自动终止。如果程序停止运行并且物理何时发生错误都输出堆栈踪迹,会让大多数用户感到困惑!

try和catch

try以及catch语句构成一个单元。catch子句的作用域被限制在由之前try语句指定的那些语句内。catch语句不能捕获由另外一条try语句抛出的异常,只能由另外的try与其组合的catch来捕获。

大部分设计良好的catch子句,都应当能够分辨出异常情况,然后继续执行,就好像错误根本没有发生一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void TestTryCatch()
{
try {
int a = 0;
int b = 42 / a;
int c[] = {1};
c[42] = 99;
} catch (ArithmeticException e) {
System.out.println("Divide by 0 :" + e);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Array index oob :" + e);
}
}

throw

对于异常,程序可以使用throw语句显示地抛出。throw语句之后的执行流会立即停止,所有后续语句都不会执行。检测最近的try代码块,查看是否存在和异常类型相匹配的catch语句。如果没有,就检测下一条try语句,这个过程一直重复下去。如果最终都没有找到匹配的catch语句,那么默认的异常处理程序终止程序并输出堆栈踪迹。

1
2
3
4
5
6
7
8
public static void TestThrow()
{
try {
throw new NullPointerException("test");
} catch (NullPointerException e) {
System.out.println("catch null point exception :" + e);
}
}

throws

如果方法可能引发自身不进行处理的异常,就必须指明这种行为,以方便方法的调用者能够保卫它们自己以防备上述异常。可以通过在方法声明中提供throws子句来完成该任务。throws子句列出了方法可能抛出的异常类型。除了Error和RuntimeException及其子类型类型的异常之外,对于所有其他类型的异常都是必须的。方法可能抛出的所有其他异常都必须在throws子句中进行声明。如果没有这么做,就会产生编译时错误。

1
2
3
4
public static void TestThrows() throws IllegalAccessException
{
throw new IllegalAccessException("test");
}

finally

当抛出异常后,方法中的执行流会采用一条非常突然、非线性的路径,这将改变方法的正常执行流。根据方法的编码方式,异常甚至可能使方法比预期时间更早地返回。对于某些方法,这可能是一个问题。例如,如果方法在开始时打开一个文件,并在结束时关闭这个文件,那么我们可能不希望关闭文件的代码绕过异常处理机制。关键字finally就是为了解决这种可能情况而设计的。

使用finally可以创建一个代码块,该代码块会在执行try/catch代码块之后,并在执行try/catch代码块后面的代码之前执行。不管是否有异常抛出,都会执行finally代码块。如果抛出异常,那么即使没有catch语句能匹配异常,finally代码块仍将执行。finally子句是可选的。

1
2
3
4
5
6
7
8
9
10
public static void TestFinally()
{
try {
throw new NullPointerException("test");
} catch (NullPointerException e) {
System.out.println("catch null point exception :" + e);
} finally {
System.out.println("enter finally");
}
}

内置异常

  • 未经检查异常

在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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MyException extends Exception
{
private int detail;

MyException(int a)
{
detail = a;
}

public String toString()
{
return "MyException[" + detail + "]";
}

}


public static void main(String[] args)
{
try {
throw new MyException(10);
} catch (MyException e) {
System.out.println(e);
}
}

链式异常

通过链式异常,可以为异常关联另一个异常。第二个异常描述第一个异常的原因(描述当前异常原因的异常,在后面通常称为“引发异常”或“背后异常”)。

为了使用链式异常,向Throwable类添加了两个构造函数和方法。

  • Throwable(Throwable causeExc)
  • Throwable(String msg, Throwable causeExc)
  • Throwable getCause()
  • Throwable initCause(Throwable causeExc)