溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何在Java中使用try-with-resource語句

發(fā)布時(shí)間:2021-03-20 15:11:23 來源:億速云 閱讀:166 作者:Leah 欄目:編程語言

如何在Java中使用try-with-resource語句?相信很多沒有經(jīng)驗(yàn)的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。

public class Demo {
  public static void main(String[] args) {
    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")));
      int b;
      while ((b = bin.read()) != -1) {
        bout.write(b);
      }
    }
    catch (IOException e) {
      e.printStackTrace();
    }
    finally {
      if (bin != null) {
        try {
          bin.close();
        }
        catch (IOException e) {
          e.printStackTrace();
        }
        finally {
          if (bout != null) {
            try {
              bout.close();
            }
            catch (IOException e) {
              e.printStackTrace();
            }
          }
        }
      }
    }
  }
}

Oh My God?。?!關(guān)閉資源的代碼竟然比業(yè)務(wù)代碼還要多?。?!這是因?yàn)?,我們不僅需要關(guān)閉BufferedInputStream,還需要保證如果關(guān)閉BufferedInputStream時(shí)出現(xiàn)了異常, BufferedOutputStream也要能被正確地關(guān)閉。所以我們不得不借助finally中嵌套finally大法??梢韵氲?,打開的資源越多,finally中嵌套的將會(huì)越深?。。?/p>

更為可惡的是,Python程序員面對這個(gè)問題,竟然微微一笑很傾城地說:“這個(gè)我們一點(diǎn)都不用考慮的嘞~”:

但是兄弟莫慌!我們可以利用Java 1.7中新增的try-with-resource語法糖來打開資源,而無需碼農(nóng)們自己書寫資源來關(guān)閉代碼。媽媽再也不用擔(dān)心我把手寫斷掉了!我們用try-with-resource來改寫剛才的例子:

public class TryWithResource {
  public static void main(String[] args) {
    try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
       BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
      int b;
      while ((b = bin.read()) != -1) {
        bout.write(b);
      }
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
}

是不是很簡單?是不是很刺激?再也不用被Python程序員鄙視了!好了,下面將會(huì)詳細(xì)講解其實(shí)現(xiàn)原理以及內(nèi)部機(jī)制。

動(dòng)手實(shí)踐

為了能夠配合try-with-resource,資源必須實(shí)現(xiàn)AutoClosable接口。該接口的實(shí)現(xiàn)類需要重寫close方法:

public class Connection implements AutoCloseable {
  public void sendData() {
    System.out.println("正在發(fā)送數(shù)據(jù)");
  }
  @Override
  public void close() throws Exception {
    System.out.println("正在關(guān)閉連接");
  }
}

調(diào)用類:

public class TryWithResource {
  public static void main(String[] args) {
    try (Connection conn = new Connection()) {
      conn.sendData();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

運(yùn)行后輸出結(jié)果:

正在發(fā)送數(shù)據(jù)
正在關(guān)閉連接

通過結(jié)果我們可以看到,close方法被自動(dòng)調(diào)用了。

原理

那么這個(gè)是怎么做到的呢?我相信聰明的你們一定已經(jīng)猜到了,其實(shí),這一切都是編譯器大神搞的鬼。我們反編譯剛才例子的class文件:

public class TryWithResource {
  public TryWithResource() {
  }
  public static void main(String[] args) {
    try {
      Connection e = new Connection();
      Throwable var2 = null;
      try {
        e.sendData();
      } catch (Throwable var12) {
        var2 = var12;
        throw var12;
      } finally {
        if(e != null) {
          if(var2 != null) {
            try {
              e.close();
            } catch (Throwable var11) {
              var2.addSuppressed(var11);
            }
          } else {
            e.close();
          }
        }
      }
    } catch (Exception var14) {
      var14.printStackTrace();
    }
  }
}

看到?jīng)],在第15~27行,編譯器自動(dòng)幫我們生成了finally塊,并且在里面調(diào)用了資源的close方法,所以例子中的close方法會(huì)在運(yùn)行的時(shí)候被執(zhí)行。

異常屏蔽

我相信,細(xì)心的你們肯定又發(fā)現(xiàn)了,剛才反編譯的代碼(第21行)比遠(yuǎn)古時(shí)代寫的代碼多了一個(gè)addSuppressed。為了了解這段代碼的用意,我們稍微修改一下剛才的例子:我們將剛才的代碼改回遠(yuǎn)古時(shí)代手動(dòng)關(guān)閉異常的方式,并且在sendData和close方法中拋出異常:

public class Connection implements AutoCloseable {
  public void sendData() throws Exception {
    throw new Exception("send data");
  }
  @Override
  public void close() throws Exception {
    throw new MyException("close");
  }
}

修改main方法:

public class TryWithResource {
  public static void main(String[] args) {
    try {
      test();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
  private static void test() throws Exception {
    Connection conn = null;
    try {
      conn = new Connection();
      conn.sendData();
    }
    finally {
      if (conn != null) {
        conn.close();
      }
    }
  }
}

運(yùn)行之后我們發(fā)現(xiàn):

basic.exception.MyException: close
at basic.exception.Connection.close(Connection.java:10)
at basic.exception.TryWithResource.test(TryWithResource.java:82)
at basic.exception.TryWithResource.main(TryWithResource.java:7)
......

好的,問題來了,由于我們一次只能拋出一個(gè)異常,所以在最上層看到的是最后一個(gè)拋出的異?!簿褪莄lose方法拋出的MyException,而sendData拋出的Exception被忽略了。這就是所謂的異常屏蔽。由于異常信息的丟失,異常屏蔽可能會(huì)導(dǎo)致某些bug變得極其難以發(fā)現(xiàn),程序員們不得不加班加點(diǎn)地找bug,如此毒瘤,怎能不除!幸好,為了解決這個(gè)問題,從Java 1.7開始,大佬們?yōu)門hrowable類新增了addSuppressed方法,支持將一個(gè)異常附加到另一個(gè)異常身上,從而避免異常屏蔽。那么被屏蔽的異常信息會(huì)通過怎樣的格式輸出呢?我們再運(yùn)行一遍剛才用try-with-resource包裹的main方法:

java.lang.Exception: send data
	at basic.exception.Connection.sendData(Connection.java:5)
	at basic.exception.TryWithResource.main(TryWithResource.java:14)
	......
	Suppressed: basic.exception.MyException: close
		at basic.exception.Connection.close(Connection.java:10)
		at basic.exception.TryWithResource.main(TryWithResource.java:15)
		... 5 more

可以看到,異常信息中多了一個(gè)Suppressed的提示,告訴我們這個(gè)異常其實(shí)由兩個(gè)異常組成,MyException是被Suppressed的異常??上部少R!

一個(gè)小問題

在使用try-with-resource的過程中,一定需要了解資源的close方法內(nèi)部的實(shí)現(xiàn)邏輯。否則還是可能會(huì)導(dǎo)致資源泄露。

舉個(gè)例子,在Java BIO中采用了大量的裝飾器模式。當(dāng)調(diào)用裝飾器的close方法時(shí),本質(zhì)上是調(diào)用了裝飾器內(nèi)部包裹的流的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 (IOException e) {
      e.printStackTrace();
    }
  }
}

在上述代碼中,我們從FileInputStream中讀取字節(jié),并且寫入到GZIPOutputStream中。GZIPOutputStream實(shí)際上是FileOutputStream的裝飾器。由于try-with-resource的特性,實(shí)際編譯之后的代碼會(huì)在后面帶上finally代碼塊,并且在里面調(diào)用fin.close()方法和out.close()方法。我們再來看GZIPOutputStream類的close方法:

public void close() throws IOException {
  if (!closed) {
    finish();
    if (usesDefaultDeflater)
      def.end();
    out.close();
    closed = true;
  }
}

我們可以看到,out變量實(shí)際上代表的是被裝飾的FileOutputStream類。在調(diào)用out變量的close方法之前,GZIPOutputStream還做了finish操作,該操作還會(huì)繼續(xù)往FileOutputStream中寫壓縮信息,此時(shí)如果出現(xiàn)異常,則會(huì)out.close()方法被略過,然而這個(gè)才是最底層的資源關(guān)閉方法。正確的做法是應(yīng)該在try-with-resource中單獨(dú)聲明最底層的資源,保證對應(yīng)的close方法一定能夠被調(diào)用。在剛才的例子中,我們需要單獨(dú)聲明每個(gè)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 (IOException e) {
      e.printStackTrace();
    }
  }
}

看完上述內(nèi)容,你們掌握如何在Java中使用try-with-resource語句的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI