在我們的項目中,所有被打開的系統資源,比如流、檔案或者Socket連接配接等,都需要被開發者手動關閉,否則随着程式的不斷運作,資源洩露将會累積成重大的生産事故。
傳統的try
文法
try{
//進行可能出現異常的操作
}catch(捕獲的異常名稱){
//進行異常處理
}finally{
//關閉資源操作
執行個體代碼如下:
public static void try1() {
BufferedInputStream bin = null;
BufferedOutputStream bout = null;
try {
bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bin != null) {
try {
bin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bout != null) {// 確定即使BufferedInputStream在執行close()方法時發生異常,也執行BufferedOutputStream的close()方法
try {
bout.close();
} catch
- 使用finally塊來關閉實體資源,保證關閉操作總是會被執行。
- 關閉每個資源之前首先保證引用該資源的引用變量不為null。
- 為每一個實體資源使用單獨的try…catch塊來關閉資源,保證關閉資源時引發的異常不會影響其他資源的關閉。
- 以上方式導緻finally塊代碼十分臃腫,關閉資源的代碼比業務代碼還要多,程式的可讀性降低。
java7增強try
為了解決以上傳統方式的問題, Java7新增了自動關閉資源的try語句。它允許在try關鍵字後緊跟一對圓括号,裡面可以聲明、初始化一個或多個資源,此處的資源指的是那些必須在程式結束時顯示關閉的資源(資料庫連接配接、網絡連接配接等),try語句會在該語句結束時自動關閉這些資源。
文法
try(初始化可能出現異常的操作){
//進行操作
}ctach(捕獲的異常名稱){
//進行異常操作
打開一個資源
try(
打開一個資源的語句;//末尾可以加";",也可以不加
){
//進行操作
打開多個資源
try(
打開一個資源的語句;
打開一個資源的語句;
打開一個資源的語句;//多個表達式末尾加";",最後一個表達式末尾可以不加";"
){
//進行操作
}
改寫以上代碼如下:
//捕獲異常
public static void try2() {
try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")));) {
// 執行完try中的語句過後,資源自動關閉
} catch
或者
//抛出異常
public static void try2() throws FileNotFoundException, IOException {
try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")));) {
// 執行完try中的語句過後,資源自動關閉
自動關閉資源的try語句相當于包含了隐式的finally塊(用于關閉資源),是以這個try語句可以既沒有catch塊,也沒有finally塊。
動手測試
為了能夠配合try-with-resource,資源必須實作AutoCloseable接口。且該接口的實作類需要重寫close方法:
package tryTest;
/**
*
* @ClassName: Connection
* @Description: 建立執行個體,實作AutoCloseable接口
* @author cheng
* @date
public class Connection implements AutoCloseable
public void sendData() throws Exception {
System.out.println("正在發送資料........");
}
/**
* 重寫close方法
*/
@Override
public void close() throws Exception {
System.out.println("正在自動關閉連接配接........");
}
}
測試
public static void try3() {
try (Connection conn = new Connection();) {
conn.sendData();
} catch
運作結果:
正在發送資料........
正在自動關閉連接配接........
通過結果我們可以看到,close方法被自動調用了。
異常屏蔽處理
修改Connection,模拟抛出異常
package tryTest;
/**
*
* @ClassName: Connection
* @Description: 建立執行個體,實作AutoCloseable接口
* @author cheng
* @date
public class Connection implements AutoCloseable
public void sendData() throws Exception {
System.out.println("正在發送資料........");
throw new Exception("模拟發送時發生了異常.....");
}
/**
* 重寫close方法
*/
@Override
public void close() throws Exception {
System.out.println("正在自動關閉連接配接........");
throw new Exception("模拟關閉時發生了異常.....");
}
}
使用傳統的try,手動關閉異常
public static void try5() throws Exception {
Connection conn = null;
try {
conn = new Connection();
conn.sendData();
} finally {
if (conn != null) {
conn.close();
}
}
}
調用:
public static void main(String[] args) {
try {
try5();
} catch
運作結果:
正在發送資料........
正在自動關閉連接配接........
java.lang.Exception: 模拟關閉時發生了異常.....
at tryTest.Connection.close(Connection.java:23)
at tryTest.TryTest.try5(TryTest.java:40)
at tryTest.TryTest.main(TryTest.java:26)
好的,問題來了,由于我們一次隻能抛出一個異常,是以在最上層看到的是最後一個抛出的異常——也就是close方法抛出的模拟異常,而sendData抛出的Exception被忽略了。這就是所謂的異常屏蔽。
由于異常資訊的丢失,異常屏蔽可能會導緻某些bug變得極其難以發現,程式員們不得不加班加點地找bug.
為了解決這個問題,從Java 1.7開始,大佬們為Throwable類新增了addSuppressed方法,支援将一個異常附加到另一個異常身上,進而避免異常屏蔽。那麼被屏蔽的異常資訊會通過怎樣的格式輸出呢?我們再運作一遍剛才用try-with-resource包裹的main方法:
public static void try3() {
try (Connection conn = new Connection();) {
conn.sendData();
} catch
運作結果:
正在發送資料........
正在自動關閉連接配接........
java.lang.Exception: 模拟發送時發生了異常.....
at tryTest.Connection.sendData(Connection.java:14)
at tryTest.TryTest.try3(TryTest.java:59)
at tryTest.TryTest.main(TryTest.java:23)
Suppressed: java.lang.Exception: 模拟關閉時發生了異常.....
at tryTest.Connection.close(Connection.java:23)
at tryTest.TryTest.try3(TryTest.java:60)
... 1
可以看到,異常資訊中多了一個Suppressed的提示,告訴我們這個異常其實由兩個異常組成.
打開多個資源時:單獨聲明每個資源
在使用try-with-resource的過程中,一定需要了解資源的close方法内部的實作邏輯。否則還是可能會導緻資源洩露。
比如,在Java BIO中采用了大量的裝飾器模式。當調用裝飾器的close方法時,本質上是調用了裝飾器内部包裹的流的close方法。比如:
public class TryWithResource {
public static void main(String[] args) {
try (FileInputStream fin = new FileInputStream(new File("input.txt"));
GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(new File("out.txt")))) {
byte[] buffer = new byte[4096];
int read;
while ((read = fin.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
}
catch
在上述代碼中,我們從FileInputStream中讀取位元組,并且寫入到GZIPOutputStream中。GZIPOutputStream實際上是FileOutputStream的裝飾器。由于try-with-resource的特性,實際編譯之後的代碼會在後面帶上finally代碼塊,并且在裡面調用fin.close()方法和out.close()方法。我們再來看GZIPOutputStream類的close方法:
public void close() throws IOException {
if (!closed) {
finish();
if (usesDefaultDeflater)
def.end();
out.close();
closed = true;
}
}
我們可以看到,out變量實際上代表的是被裝飾的FileOutputStream類。在調用out變量的close方法之前,GZIPOutputStream還做了finish操作,該操作還會繼續往FileOutputStream中寫壓縮資訊,此時如果出現異常,則會out.close()方法被略過,然而這個才是最底層的資源關閉方法。正确的做法是應該在try-with-resource中單獨聲明最底層的資源,保證對應的close方法一定能夠被調用。在剛才的例子中,我們需要單獨聲明每個FileInputStream以及FileOutputStream:
public class TryWithResource {
public static void main(String[] args) {
try (FileInputStream fin = new FileInputStream(new File("input.txt"));
FileOutputStream fout = new FileOutputStream(new File("out.txt"));
GZIPOutputStream out = new GZIPOutputStream(fout)) {
byte[] buffer = new byte[4096];
int read;
while ((read = fin.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
}
catch
由于編譯器會自動生成fout.close()的代碼,這樣肯定能夠保證真正的流被關閉。
總結:同時打開多個資源時,單獨聲明每個資源!
注意
- 被自動關閉的資源必須實作Closeable或AutoCloseable接口。(Closeable是AutoCloseable的子接口,Closeable接口裡的close()方法聲明抛出了IOException,;AutoCloseable接口裡的close()方法聲明抛出了Exception)
- 被關閉的資源必須放在try語句後的圓括号中聲明、初始化。如果程式有需要自動關閉資源的try語句後可以帶多個catch塊和一個finally塊。
- Java7幾乎把所有的“資源類”(包括檔案IO的各種類,JDBC程式設計的Connection、Statement等接口……)進行了改寫,改寫後的資源類都實作了AutoCloseable或Closeable接口
AutoCloseable 源碼:
package java.lang;
public interface AutoCloseable
void close() throws
Closeable源碼:
package java.lang;
public interface Closeable implements AutoCloseable
void close() throws
若我們在try中打開的資源類沒有實作AutoCloseable接口或者Closeable接口,則會報以下錯誤:
The resource type Connection does not
捕獲多個異常
當我們操作一個對象的時候,它有時候會抛出多個異常,例如:
public static void try6() {
try {
Thread.sleep(20000);
FileInputStream fis = new FileInputStream("/a/b,txt");
} catch (InterruptedException e) {
e.printStackTrace();
} catch
這樣寫起來很繁瑣,java7優化後代碼如下:
public static void try7() {
try {
Thread.sleep(20000);
FileInputStream fis = new FileInputStream("/a/b,txt");
} catch
并且catch語句後面的異常參數是final的,不可以再修改
處理反射異常
java7之前使用反射時會有許多需要處理的異常,代碼如下:
public static void try7() {
Object object = null;
Class<?> clazz;
try {
clazz = Class.forName("tryTest.Connection");
clazz.getMethods()[0].invoke(object);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch
public static void try7() {
Object object = null;
Class<?> clazz;
try {
clazz = Class.forName("tryTest.Connection");
clazz.getMethods()[0].invoke(object);
} catch
public static void try7() {
Object object = null;
Class<?> clazz;
try {
clazz = Class.forName("tryTest.Connection");
clazz.getMethods()[0].invoke(object);
} catch