java優雅的關閉資源 : try-with-resources (effect java 學習筆記 9)

客官,往這瞅java

背景

  在咱們平常的開發過程當中,會調用須要手動close的資源。好比InputStream, OutputStream ,java.sql.Connection,socket等。別想着java有了GC,GC大大說,不是我家的,誰愛用誰管。得嘞,咱們本身管。sql


 沒有對比就沒有傷害,咱們來傷害吧!編程

對比傷害

1、try-finally

  從以往來看,try-finally語句是保證資源正確關閉的最佳方式,即便是在程序拋出異常或返回的狀況下:bash

static String firstLineOfFile(String path) throws IOException {

        BufferedReader  br = new BufferedReader(new FileReader(path));
        try {
            return br.readLine();
        } finally {
                br.close();
            }
        }
        return "";
    }
複製代碼

這樣看起來並不壞,可是當添加第二個資源時,狀況就會變得更糟:socket

static void copy(String src, String dst) throws IOException {
        InputStream in = new FileInputStream(src);

        try {
            OutputStream out = new FileOutputStream(dst);
            try {
                byte[] buf = new byte[1024];
                int n;
                while ((n = in.read(buf)) >= 0) {
                    out.write(buf, 0, n);
                }
            } finally {
                out.close();
            }
        } finally {
            in.close();
        }
        
    }
複製代碼

  即便是用 try-finally 語句關閉資源的正確代碼,如前面兩個代碼示例所示,也有一個微妙的缺陷。 try-with- resources 塊和 finally 塊中的代碼均可以拋出異常。 例如,在 firstLineOfFile 方法中,因爲底層物理設備發生 故障,對 readLine 方法的調用可能會引起異常,而且因爲相同的緣由,調用 close 方法可能會失敗。 在這種 狀況下,第二個異常徹底沖掉了第一個異常。 在異常堆棧跟蹤中沒有第一個異常的記錄,這可能使實際系統中的調 試很是複雜——一般這是你想要診斷問題的第一個異常。 雖然能夠編寫代碼來抑制第二個異常,可是實際上沒有人這 樣作,由於它太冗長了。ui


2、try-with-resources

  當java 7 引入了try-with-resources語句後,這些就都解決了。要使用這個語法糖,資源必須都實現AutoCloseAble接口.Java 類庫和第三方庫中的許多類和接口如今都實現了或繼承了AutoCloseable接口。若是本身編寫的類表示必須關閉的資源,那麼也應該實現AutoCloseable接口。spa

public interface AutoCloseable {
    void close() throws Exception;
}
複製代碼

修改後的實例以下:3d

static String firstLineOfFile(String path) throws IOException { 
    try (BufferedReader br = new BufferedReader( 
        new FileReader(path))) { 
        return br.readLine(); 
        } 
}
複製代碼

第二個實例:code

static void copy(String src, String dst) throws IOException {
        try (InputStream in = new FileInputStream(src);
             OutputStream out = new FileOutputStream(dst)) {
            byte[] buf = new byte[1024];
            int n;
            while ((n = in.read(buf)) >= 0)
                out.write(buf, 0, n);
        }
    }
複製代碼

  不只 try-with-resources 版本比原始版本更精簡,更好的可讀性,並且它們提供了更好的診斷。 考慮 firstLineOfFile 方法。 若是調用 readLine 和(不可見) close 方法都拋出異常,則後一個異常將被抑制 (suppressed),而不是前者。 事實上,爲了保留你真正想看到的異常,可能會抑制多個異常。 這些抑制的異常沒 有被拋棄, 而是打印在堆棧跟蹤中,並標註爲被抑制了。 你也能夠使用 getSuppressed 方法以編程方式訪問它 們,該方法在 Java 7 中已添加到的 Throwable 中。cdn

是否是很神奇,來讓咱們看看原理吧


原理:

下面是第二個實例編譯後的代碼,看了是否是恍然大悟,原來一切都是編譯器幫咱們作好了

static void copy(String src, String dst) throws IOException {
        InputStream in = new FileInputStream(src);
        Throwable var3 = null;

        try {
            OutputStream out = new FileOutputStream(dst);
            Throwable var5 = null;

            try {
                byte[] buf = new byte[1024];

                int n;
                while((n = in.read(buf)) >= 0) {
                    out.write(buf, 0, n);
                }
            } catch (Throwable var29) {
                var5 = var29;
                throw var29;
            } finally {
                if (out != null) {
                    if (var5 != null) {
                        try {
                            out.close();
                        } catch (Throwable var28) {
                            var5.addSuppressed(var28);
                        }
                    } else {
                        out.close();
                    }
                }

            }
        } catch (Throwable var31) {
            var3 = var31;
            throw var31;
        } finally {
            if (in != null) {
                if (var3 != null) {
                    try {
                        in.close();
                    } catch (Throwable var27) {
                        var3.addSuppressed(var27);
                    }
                } else {
                    in.close();
                }
            }

        }

    }
複製代碼

使用細節:

  相信細心的看官都發現了,實例的資源都是聲明在括號中,而且實例化的吧。來咱們繼續說說怎麼回事, java 9之前:

static String readData(String message) throws IOException {

      Reader inputString = new StringReader(message);
      BufferedReader br = new BufferedReader(inputString);
      try (BufferedReader br1 = br) {
         return br1.readLine();
      }
   }
複製代碼

若是不這樣作,br資源不能釋放,這個咱們須要注意,可是在java 9之後,就不用額外聲明變量了

static String readData(String message) throws IOException {

      Reader inputString = new StringReader(message);
      BufferedReader br = new BufferedReader(inputString);
      try (br) {
         return br.readLine();
      }
   }
複製代碼

總結:

  結論很明確:在處理必須關閉的資源時,使用 try-with-resources 語句替代 try-finally 語句。 生成的代碼更簡潔, 更清晰,而且生成的異常更有用。 try-with-resources 語句在編寫必須關閉資源的代碼時會更容易,也不會出錯,而 使用 try-finally 語句其實是不可能的。

)
相關文章
相關標籤/搜索