下面的方法將一個(gè)文件拷貝到另一個(gè)文件,并且被設(shè)計(jì)為要關(guān)閉它所創(chuàng)建的每一個(gè)流,即使它碰到I/O錯(cuò)誤也要如此。遺憾的是,它并非總是能夠做到這一點(diǎn)。為什么不能呢,你如何才能訂正它呢?
static void copy(String src, String dest) throws IOException {
InputStream in = null;
OutputStream out = null;
try {
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();
}
}
這個(gè)程序看起來(lái)已經(jīng)面面俱到了。其流域(in和out)被初始化為null,并且新的流一旦被創(chuàng)建,它們馬上就被設(shè)置為這些流域的新值。對(duì)于這些域所引用的流,如果不為空,則finally語(yǔ)句塊會(huì)將其關(guān)閉。即便在拷貝操作引發(fā)了一個(gè)IOException的情況下,finally語(yǔ)句塊也會(huì)在方法返回之前執(zhí)行。出什么錯(cuò)了呢?
問(wèn)題在finally語(yǔ)句塊自身中。close方法也可能會(huì)拋出IOException異常。如果這正好發(fā)生在in.close被調(diào)用之時(shí),那么這個(gè)異常就會(huì)阻止out.close被調(diào)用,從而使輸出流仍保持在開放狀態(tài)。
請(qǐng)注意,該程序違反了謎題36的建議:對(duì)close的調(diào)用可能會(huì)導(dǎo)致finally語(yǔ)句塊意外結(jié)束。遺憾的是,編譯器并不能幫助你發(fā)現(xiàn)此問(wèn)題,因?yàn)閏lose方法拋出的異常與read和write拋出的異常類型相同,而其外圍方法(copy)聲明將傳播該異常。
解決方式是將每一個(gè)close都包裝在一個(gè)嵌套的try語(yǔ)句塊中。下面的finally語(yǔ)句塊的版本可以保證在兩個(gè)流上都會(huì)調(diào)用close:
} 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
}
}
}
從5.0版本開始,你可以對(duì)代碼進(jìn)行重構(gòu),以利用Closeable接口:
} finally {
closeIgnoringException(in);
closeIgnoringEcception(out);
}
private static void closeIgnoringException(Closeable c) {
if (c != null) {
try {
c.close();
} catch (IOException ex) {
// There is nothing we can do if close fails
}
}
}
總之,當(dāng)你在finally語(yǔ)句塊中調(diào)用close方法時(shí),要用一個(gè)嵌套的try-catch語(yǔ)句來(lái)保護(hù)它,以防止IOException的傳播。更一般地講,對(duì)于任何在finally語(yǔ)句塊中可能會(huì)拋出的被檢查異常都要進(jìn)行處理,而不是任其傳播。這是謎題36中的教訓(xùn)的一種特例,而對(duì)語(yǔ)言設(shè)計(jì)著的教訓(xùn)情況也相同。
static void copy(String src, String dest) throws IOException {
InputStream in = null;
OutputStream out = null;
try {
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();
}
}
這個(gè)程序看起來(lái)已經(jīng)面面俱到了。其流域(in和out)被初始化為null,并且新的流一旦被創(chuàng)建,它們馬上就被設(shè)置為這些流域的新值。對(duì)于這些域所引用的流,如果不為空,則finally語(yǔ)句塊會(huì)將其關(guān)閉。即便在拷貝操作引發(fā)了一個(gè)IOException的情況下,finally語(yǔ)句塊也會(huì)在方法返回之前執(zhí)行。出什么錯(cuò)了呢?
問(wèn)題在finally語(yǔ)句塊自身中。close方法也可能會(huì)拋出IOException異常。如果這正好發(fā)生在in.close被調(diào)用之時(shí),那么這個(gè)異常就會(huì)阻止out.close被調(diào)用,從而使輸出流仍保持在開放狀態(tài)。
請(qǐng)注意,該程序違反了謎題36的建議:對(duì)close的調(diào)用可能會(huì)導(dǎo)致finally語(yǔ)句塊意外結(jié)束。遺憾的是,編譯器并不能幫助你發(fā)現(xiàn)此問(wèn)題,因?yàn)閏lose方法拋出的異常與read和write拋出的異常類型相同,而其外圍方法(copy)聲明將傳播該異常。
解決方式是將每一個(gè)close都包裝在一個(gè)嵌套的try語(yǔ)句塊中。下面的finally語(yǔ)句塊的版本可以保證在兩個(gè)流上都會(huì)調(diào)用close:
} 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
}
}
}
從5.0版本開始,你可以對(duì)代碼進(jìn)行重構(gòu),以利用Closeable接口:
} finally {
closeIgnoringException(in);
closeIgnoringEcception(out);
}
private static void closeIgnoringException(Closeable c) {
if (c != null) {
try {
c.close();
} catch (IOException ex) {
// There is nothing we can do if close fails
}
}
}
總之,當(dāng)你在finally語(yǔ)句塊中調(diào)用close方法時(shí),要用一個(gè)嵌套的try-catch語(yǔ)句來(lái)保護(hù)它,以防止IOException的傳播。更一般地講,對(duì)于任何在finally語(yǔ)句塊中可能會(huì)拋出的被檢查異常都要進(jìn)行處理,而不是任其傳播。這是謎題36中的教訓(xùn)的一種特例,而對(duì)語(yǔ)言設(shè)計(jì)著的教訓(xùn)情況也相同。