使用 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 。)