利用 Java SE 7 异常处理方面的改进

作者:Manfred Riem

了解如何利用改进的异常处理,这是 Java SE 7 中 Coin 项目在语言方面众多有用的小改动中的一个。

2011 年 9 月发布

下载:

下载Java SE 7

简介

在本文中,我们将介绍 Java Platform, Standard Edition 7 (Java SE 7) 版本中的一些变化,JSR 334(又称为 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

过去,如果希望上面三个用例中的两个,比如 ParseExceptionIOException,具有相同的逻辑,您必须复制和粘贴相同的代码。经验不足或懒惰的程序员可能认为像下面这样做就可以了:

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 子句吞掉。如果抛出的异常不是 ParseExceptionIOException(例如,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 子句将同时捕获 ParseExceptionIOException

因此,如果您现在希望两种不同的异常共享相同的异常处理代码,可以使用管道语法:(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) 技术;而作为业务爱好,他喜欢使用各种语言和工具。