利用 Java SE 7 异常处理方面的改进 作者:Manfred Riem
了解如何利用改进的异常处理,这是 Java SE 7 中 Coin 项目在语言方面众多有用的小改动中的一个。 2011 年 9 月发布
下载:
简介
在本文中,我们将介绍 Java Platform, Standard Edition 7 (Java SE 7) 版本中的一些变化,Coin 项目)中对此进行了详细说明。我们重点介绍异常处理,具体来说就是多重捕获、重新抛出和 try-with-resources。
Coin 项目由下面这些语言方面的小改动组成,旨在简化常见的日常编程任务: switch 语句中的字符串
更好的整型字符串
本文所介绍的多重捕获异常
改进了泛型实例创建的类型推断(“<>”)
本文所介绍的 try-with-resources
简化了可变参数方法调用
多重捕获异常
Java SE 7 中新增了多重捕获异常,以便更轻松更简洁地处理异常。要将您的异常处理代码从 Java SE 7 之前的代码迁移到 Java SE 7 代码,请继续阅读。 public class ExampleExceptionHandling
{
public static void main( String[] args )
{
try {
URL url = new URL("http://www.yoursimpledate.server/");
BufferedReader reader = new
BufferedReader(newInputStreamReader(url.openStream()));
String line = reader.readLine();
SimpleDateFormat format = new SimpleDateFormat("MM/DD/YY");
Date date = format.parse(line);
}
catch(ParseException exception) {
// handle passing in the wrong type of URL.
}
catch(IOException exception) {
// handle I/O problems.
}
catch(ParseException exception) {
// handle date parse problems.
}
}
}
示例 1
过去,如果希望上面三个用例中的两个,比如 ParseException 和 IOException,具有相同的逻辑,您必须复制和粘贴相同的代码。经验不足或懒惰的程序员可能认为像下面这样做就可以了: public class ExampleExceptionHandlingLazy
{
public static void main( String[] args )
{
try {
URL url = new URL("http://www.yoursimpledate.server/");
BufferedReader reader = new
BufferedReader(new InputStreamReader(url.openStream()));
String line = reader.readLine();
SimpleDateFormat format = new SimpleDateFormat("MM/DD/YY");
Date date = format.parse(line);
}
catch(Exception exception) {
// I am an inexperienced or lazy programmer here.
}
}
}
示例 2
示例 2 中代码的最大问题在于它可能会带来意想不到的副作用。try 代码块中的任何代码都可能会抛出异常,而该异常将被一个覆盖式 (Exception) catch 子句吞掉。如果抛出的异常不是 ParseException 或 IOException(例如,SecurityException),该代码仍然会捕获它,但上游用户不知道实际发生了什么。像这样吞掉异常会导致很难对问题进行调试。
为了方便程序员工作,Java SE 7 现在包括了多重捕获语句。这就允许程序员将 catch 子句组合成一个代码块,而无需使用危险的 catch-all 子句或复制整个代码块。 public class ExampleExceptionHandlingNew
{
public static void main( String[] args )
{
try {
URL url = new URL("http://www.yoursimpledate.server/");
BufferedReader reader = new BufferedReader(
new InputStreamReader(url.openStream()));
String line = reader.readLine();
SimpleDateFormat format = new SimpleDateFormat("MM/DD/YY");
Date date = format.parse(line);
}
catch(ParseException | IOException exception) {
// handle our problems here.
}
}
}
示例 3
示例 3 显示了如何将两条语句正确合并成一个 catch 代码块。注意 catch 子句的语法 (ParseException | IOException)。此 catch 子句将同时捕获 ParseException 和 IOException。
因此,如果您现在希望两种不同的异常共享相同的异常处理代码,可以使用管道语法:(ExceptionType| ...| ExceptionType 变量)。
重新抛出异常
执行异常处理时,有时您希望重新抛出已经处理过的异常。经验不足的程序员可能认为以下代码可以完成此操作: public class ExampleExceptionRethrowInvalid
{
public static void demoRethrow()throws IOException {
try { // forcing an IOException here as an example,
// normally some code could trigger this.
throw new IOException(“Error”);
}
catch(Exception exception) {
/*
* Do some handling and then rethrow.
*/
throw exception;
}
}
public static void main( String[] args )
{
try {
demoRethrow();
}
catch(IOException exception) {
System.err.println(exception.getMessage());
}
}
}
示例 4
但编译器不会编译示例 4 中的代码。示例 5 显示了一种处理异常然后将其“重新抛出”的办法: public class ExampleExceptionRethrowOld
{
public static demoRethrow() {
try {
throw new IOException("Error");
}
catch(IOException exception) {
/*
* Do some handling and then rethrow.
*/
throw new RuntimeException(exception);
}
}
public static void main( String[] args )
{
try {
demoRethrow();
}
catch(RuntimeException exception) {
System.err.println(exception.getCause().getMessage());
}
}
}
示例 5
示例 5 的问题在于它并未真正重新抛出原始异常。而是将其嵌套在另一个异常中,这意味着下游代码需要知道原始异常已被嵌套。因此,为了能够实际捕获原始异常,需要在 Java SE 中进行修改(如示例 6 所示)。 public class ExampleExceptionRethrowSE7
{
public static demoRethrow() throws IOException {
try {
throw new IOException("Error");
}
catch(Exception exception) {
/*
* Do some handling and then rethrow.
*/
throw exception;
}
}
public static void main( String[] args )
{
try {
demoRethrow();
}
catch(IOException exception) {
System.err.println(exception.getMessage());
}
}
}
示例 6
Try-with-Resources
您可能已注意到示例 1 有一个问题(正因为如此,千万不要在生产环境中不明就理地使用示例代码)。问题是 try 代码块中未清理使用的资源。示例 7 是一个更新版本,说明了在 Java SE 7 之前程序员应如何解决此问题。 public class ExampleTryResources
{
public static void main(String[] args)
{
BufferedReader reader = null;
try {
URL url = new URL("http://www.yoursimpledate.server/");
reader = new BufferedReader(new
InputStreamReader(url.openStream()));
String line = reader.readLine();
SimpleDateFormat format = new SimpleDateFormat("MM/DD/YY");
Date date = format.parse(line);
}
catch (MalformedURLException exception) {
// handle passing in the wrong type of URL.
} catch (IOException exception) {
// handle I/O problems.
} catch (ParseException exception) {
// handle date parse problems.
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}
示例 7
注意,如果曾分配过 BufferedReader,则必须添加一个将其关闭的最终代码块。还应注意到,现在 reader 变量位于 try 代码块之外。如果您想做的只是在发生 I/O 异常时才关闭 reader,那么需要编写很多代码。
在 Java SE 7 中,可以更简洁清晰地完成这一操作,如示例 8 所示。使用新的语法,您可以声明作为 try 块组成部分的资源。这意味着您可以预先定义资源,运行时将在执行 try 代码块后自动关闭这些资源(如果尚未关闭)。 public static void main(String[] args)
{
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new URL("http://www.yoursimpledate.server/").openStream())))
{
String line = reader.readLine();
SimpleDateFormat format = new SimpleDateFormat("MM/DD/YY");
Date date = format.parse(line);
} catch (ParseException | IOException exception) {
// handle I/O problems.
}
}
示例 8
注意,在示例 8 中,实际的打开操作发生在 try ( ...) 语句中。请注意,此特性仅对实现了 AutoCloseable 接口的类起作用。
总结
Java SE 7 中的异常处理更改不仅可以让您更简洁地编程(如多重捕获示例中所演示的),还允许您对异常执行部分处理,然后再对其进行调用(如重新抛出示例中所述)。Java SE 7 还能使异常清理操作减少出错,如 try-with-resources 示例中所示。这些特性以及 Coin 项目中提供的其他功能,可以使 Java 开发人员提高工作效率,编写更高效的代码。
另请参见
关于作者
Manfred Riem 在 Systems Made Simple 担任系统架构师。他喜欢使用各种技术。在工作中,他重点关注 Java Platform, Enterprise Edition (Java EE) 技术;而作为业务爱好,他喜欢使用各种语言和工具。