【java基础(四十八)】异常(四)finally 、try - with - resources

finally子句

当代码抛出一个异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行。如果方法获得了一些本地资源,并且只有这个方法自己知道,又如果这些资源在退出方法之前必须被回收,那么就会产生资源回收问题。一种解决方案是捕获并重新抛出所有的异常。但是,这种解决方案比较乏味,因为需要在两个地方清除所分配的资源。一个在正常的代码中;另一个在异常代码中。

Java有一种更好的解决方案,这就是finally子句。如果使用Java编写数据库程序,就需要使用同样的技术关闭与数据库的连接。当发生异常时,恰当地关闭所有数据库的连接是非常重要的。

不管是否有异常被捕获,finally子句中的代码都被执行。在下面的示例中,程序将在所有情况下关闭文件:

InputStream in = new FileInputStream(...);
try {
	// 1
	...
	// 2
} catch (IOException e) {
	// 3
	...
	// 4
} finally {
	// 5
	in.close;
}
// 6

在上面这段代码中,有下列3中情况会执行finally子句:
1) 代码没有抛出异常。在这种情况下,程序首先执行try语句块中的全部代码,然后执行finally子句中的代码。随后,继续执行try语句块之后的第一条语句。也就是说,执行标注的1、2、5、6处。

2) 抛出一个在catch子句中捕获的异常。在上面的示例中就是IOException异常。在这种情况下,程序将执行try语句块中的所有代码,直到发生异常为止。此时,将跳过try语句块中的剩余代码,转去执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。
如果catch子句没有抛出异常,程序将执行try语句块之后的第一条语句。在这里,执行标注1、3、4、5、6处的语句。
如果catch子句抛出了一个异常,异常将被抛回这个方法的调用者。在这里,执行标注1、3、5处的语句。

3) 代码抛出了一个异常,但这个异常不是由catch子句捕获的。在这种情况下,程序将执行try语句块中的所有语句,直到异常被抛出为止。此时,将跳过try语句块中的剩余代码,然后执行finally子句中的语句,并将异常抛给这个方法的调用者。在这里,将执行标注1、5处的语句。

try语句可以只有finally子句,而没有catch子句。如:

InputStream in = ....;
try {
	...
} finally {
	in.close();
}

无论在try语句块中是否遇到异常,finally子句中的in.close()语句都会被执行。当然,如果真的遇到一个异常,这个异常将会被重新抛出,并且必须由另一个catch子句捕获。

有时候,finally子句也会带来麻烦。例如,清理资源的方法也有可能抛出异常。假设希望能够确保在流处理代码中遇到异常时将流关闭。

InputStream in = ....;
try {
	...
} finally {
	in.close();
}

现在,假设在try语句块中的代码抛出了一些非IOException的异常,这些异常只有这个方法的调用者才能够给予处理。执行finally语句块,并调用close方法。而close方法本身也有可能抛出IOException异常。当出现这种情况时,原始的异常将会丢失,转而抛出close方法 的异常。

这会有问题,因为第一个异常将很可能更有意思。如果你想做适当的处理,重新抛出原来的异常,代码会变得极其繁琐:

InputStream in = ...;
Exception ex = null;
try {
	try {
		...
	} catch (Exception e) {
		ex = e;
		throw e;
	} 
} finally {
	try {
		in.close()
	} catch(Exception e) {
		if (ex == null) throw e;
	}
}

幸运的是,在Java SE 7中提供了关闭资源更方便的方法。

带资源的try语句

对于以下代码模式:

open a resource
try {
	...
} finally {
	close the resource
}

假设资源属于一个实现了AutoCloseable接口的类,Java SE 7为这种代码模式提供了一个很有用的快捷方式。AutoCloseable接口有一个方法:

void close() throws Exception

带资源的try语句(try - with - resources)的最简形式为:

try (Resource res = ...) {
	...
}

try块退出时,会自动的调用res.close()。下面给出一个典型的例子,这里读取一个文件中所有单词:

try (Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words"), "UTF-8")) {
	while (in.hasNext())
		System.out.println(in.next());
}

这个块正常退出时,或者存在一个异常时,都会调用in.close()方法,就好像使用了finally块一样。

还可以指定多个资源。如:

try (Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words"), "UTF-8");
	PrintWriter out = new PrintWriter("out.txt")) {
	while (in.hasNext())
		out.println(in.next().toUpperCase());
}

不论这个块如何退出,in和out都会关闭。如果你用常规方式手动编程,就需要两个嵌套的try/finally语句。

在上面的问题中,如果try块抛出一个异常,而且close方法也抛出一个异常,这就会带来一个难题。带资源的try语句可以很好的处理这种情况。原来的异常会重新抛出,而close方法抛出的异常会“被抑制”。这些异常将自动捕获,并由addSuppressed方法增加到原来的异常。如果对这些异常感兴趣,可以调用getSuppressed方法,它会得到从close方法抛出并被抑制的异常列表。

只要需要关闭资源,就要尽可能使用带资源的try语句。

捐赠

若你感觉读到这篇文章对你有启发,能引起你的思考。请不要吝啬你的钱包,你的任何打赏或者捐赠都是对我莫大的鼓励。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章