使用 try-catch 捕获异常
可以使用 try...catch 语句捕获和处理异常。 (事实上,try 语句采用其他形式,如其他关于 try...catch...finally 和 try-with-resources 的例子所述 。)
尝试捕获一个捕获块
最简单的表单如下所示:
try {
doSomething();
} catch (SomeException e) {
handle(e);
}
// next statement
一个简单的 try...catch 的行为如下:
- 执行
try块中的语句。 - 如果
try块中的语句没有抛出异常,则控制转到try...catch之后的下一个语句。 - 如果在
try块中抛出异常。- 测试异常对象以查看它是
SomeException的实例还是子类型。 - 如果是,那么
catch块将捕获异常:- 变量
e绑定到异常对象。 - 执行
catch块中的代码。 - 如果该代码抛出异常,则传播新抛出的异常代替原始异常。
- 否则,控制传递到
try...catch之后的下一个语句。
- 变量
- 如果不是,则原始异常继续传播。
- 测试异常对象以查看它是
尝试捕获多个捕获
一个 try...catch 也可以有多个 catch 块。例如:
try {
doSomething();
} catch (SomeException e) {
handleOneWay(e)
} catch (SomeOtherException e) {
handleAnotherWay(e);
}
// next statement
如果有多个 catch 块,则从第一个块开始一次尝试一个,直到找到异常的匹配。执行相应的处理程序(如上所述),然后将控制权传递给 try...catch 语句后的下一个语句。即使处理程序代码抛出异常,也总是跳过匹配的 catch 块之后的块。
自上而下匹配策略会对 catch 块中的异常不是不相交的情况产生影响。例如:
try {
throw new RuntimeException("test");
} catch (Exception e) {
System.out.println("Exception");
} catch (RuntimeException e) {
System.out.println("RuntimeException");
}
此代码段将输出 Exception 而不是 RuntimeException。由于 RuntimeException 是 Exception 的子类型,因此第一个(更一般的)catch 将匹配。第二个(更具体的)catch 永远不会被执行。
从中学到的教训是,最具体的 catch 块(就异常类型而言)应该首先出现,而最常见的块应该是最后一块。 (如果永远不能执行 catch,某些 Java 编译器会发出警告,但这不是编译错误。)
多异常捕获块
Version >= Java SE 7
从 Java SE 7 开始,单个 catch 块可以处理不相关的异常列表。列出了异常类型,使用竖线(|)符号分隔。例如:
try {
doSomething();
} catch (SomeException | SomeOtherException e) {
handleSomeException(e);
}
多异常捕获的行为是单异常情况的简单扩展。如果抛出的异常与(至少)列出的一个异常匹配,则 catch 匹配。
规范中还有一些额外的细微之处。e 的类型是列表中异常类型的合成联合。当使用 e 的值时,其静态类型是 union 类型的最不常见的超类型。但是,如果 e 在 catch 块中重新抛出,则抛出的异常类型是 union 中的类型。例如:
public void method() throws IOException, SQLException
try {
doSomething();
} catch (IOException | SQLException e) {
report(e);
throw e;
}
在上面,IOException 和 SQLException 是检查的例外,其最不常见的超类型是 Exception。这意味着 report 方法必须匹配 report(Exception)。但是,编译器知道 throw 只能投掷 IOException 或者 SQLException。因此,method 可以声明为 throws IOException, SQLException 而不是 throws Exception。 (这是一件好事:看看陷阱 - 投掷 Throwable,Exception,Error 或 RuntimeException 。)