Java异常(三) 《Java Puzzles》中关于异常的几个谜题(四)
in = new FileInputStream(src);
out = new FileOutputStream(dest);
byte[] buf = new byte[1024];
int n;
while ((n = in.read(buf)) > 0)
out.write(buf, 0, n);
} finally {
if (in != null) in.close();
if (out != null) out.close();
}
}
复制代码
谜题分析:
这个程序看起来已经面面俱到了。其流域(in 和 out)被初始化为 null,并且新的流一旦被创建,它们马上就被设置为这些流域的新值。对于这些域所引用的流,如果不为空,则 finally 语句块会将其关闭。即便在拷贝操作引发了一个 IOException 的情况下,finally 语句块也会在方法返回之前执行。出什么错了呢
问题在 finally 语句块自身中。close 方法也可能会抛出 IOException 异常。如果这正好发生在 in.close 被调用之时,那么这个异常就会阻止 out.close 被调用,从而使输出流仍保持在开放状态。请注意,该程序违反了"优柔寡断" 的建议:对 close 的调用可能会导致 finally 语句块意外结束。遗憾的是,编译器并不能帮助你发现此问题,因为 close 方法抛出的异常与 read 和 write 抛出的异常类型相同,而其外围方法(copy)声明将传播该异常。解决方式是将每一个 close 都包装在一个嵌套的 try 语句块中。
下面的 finally 语句块的版本可以保证在两个流上都会调用 close:
复制代码
try {
// 和之前一样
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) {
// There is nothing we can do if close fails
}
}
if (out != null) {
try {
out.close();
} catch (IOException ex) {
// There is nothing we can do if close fails
}
}
}
复制代码
总之,当你在 finally 语句块中调用 close 方法时,要用一个嵌套的 try-catch 语句来保护它,以防止 IOException 的传播。更一般地讲,对于任何在 finally 语句块中可能会抛出的被检查异常都要进行处理,而不是任其传播。
谜题7: 异常为循环而抛
下面的程序会打印出什么呢
复制代码
public class Loop {
public static void main(String[] args) {
int[][] tests = { { 6, 5, 4, 3, 2, 1 }, { 1, 2 },
{ 1, 2, 3 }, { 1, 2, 3, 4 }, { 1 } };
int successCount = 0;
try {
int i = 0;
while (true) {
if (thirdElementIsThree(tests[i++]))
successCount ++;
}
} catch(ArrayIndexOutOfBoundsException e) {
// No more tests to process
}
System.out.println(successCount);
}
private static boolean thirdElementIsThree(int[] a) {
return a.length >= 3 & a[2] == 3;
}
}
复制代码
运行结果:
0
结果说明:
该程序主要说明了两个问题。
第1个问题:不应该使用异常作为终止循环的手段!
该程序用 thirdElementIsThree 方法测试了 tests 数组中的每一个元素。遍历这个数组的循环显然是非传统的循环:它不是在循环变量等于数组长度的时候终止,而是在它试图访问一个并不在数组中的元素时终止。尽管它是非传统的,但是这个循环应该可以工作。
如果传递给 thirdElementIsThree 的参数具有 3 个或更多的元素,并且其第三个元素等于 3,那么该方法将返回 true。对于 tests中的 5 个元素来说,有 2 个将返回 true,因此看起来该程序应该打印 2。如果你运行它,就会发现它打印的时 0。肯定是哪里出了问题,你能确定吗 事实上,这个程序犯了两个错误。第一个错误是该程序使用了一种可怕的循环惯用法,该惯用法依赖的是对数组的访问会抛出异常。这种惯用法不仅难以
阅读, 而且运行速度还非常地慢。不要使用异常来进行循环控制;应该只为异常条件而使用异常。为了纠正这个错误,可以将整个 try-finally 语句块替换为循环遍历数组的标准惯用法:
for (int i = 0; i < test.length; i++)
if (thirdElementIsThree(tests[i]))
successCount++;
如果你使用的是 5.0 或者是更新的版本,那么你可以用 for 循环结构来代替:
for (int[] test : tests)
if(thirdElementIsThree(test))
successCount++;
第2个问题: 主要比较"&操作符" 和 "&&操作符"的区别。注意示例中的操作符是&,这是按位进行"与"操作。