爲何effective java 第三版推薦使用try-with-resources代替try-finally

背景

try-finally 這個語句想必作java的同窗都不陌生吧,每當咱們有關閉資源的需求咱們都會使用到try-finally這個語句,好比咱們在使用鎖的時候,不管是本地的可重入鎖仍是分佈式鎖都會有下面相似的結構代碼,咱們會在finally裏面進行unlock,用於強制解鎖:java

Lock lock = new ReentrantLock();
    lock.lock();
    try{
        // doSometing
    }finally {
        lock.unlock();
    }

或者咱們使用java的文件流讀取或者寫入文件的時候,咱們也會在finally中強制關閉文件流,防止資源泄漏。數據庫

InputStream inputStream = new FileInputStream("file");
    try {
        System.out.println(inputStream.read(new byte[4]));
    }finally {
        inputStream.close();
    }

其實乍一看 這樣的寫法應該沒什麼問題,可是若是咱們出現了多個資源須要關閉咱們應該怎麼寫呢?最多見的寫法以下:分佈式

InputStream inputStream = new FileInputStream("file");
    OutputStream outStream = new FileOutputStream("file1");

    try {
        System.out.println(inputStream.read(new byte[4]));
        outStream.write(new byte[4]);
    }finally {
        inputStream.close();
        outStream.close();
    }

咱們在外面定義了兩個資源,而後在finally裏面依次對這兩個資源進行關閉,這個寫法在我最開始寫java的時候對文件流和數據庫鏈接池作close的時候一些教學的文章都是這麼教學的,那麼這個哪裏有問題呢?問題其實在於若是在inputStream.close的時候拋出異常,那麼outStream.close()就不會執行,這很明顯不是咱們想要的結果,因此後面就改爲了下面這種多重嵌套的方式去寫:ide

InputStream inputStream = new FileInputStream("file");
    try {
        System.out.println(inputStream.read(new byte[4]));
        try{
            OutputStream outStream = new FileOutputStream("file1");
            outStream.write(new byte[4]);
        }finally {
            outStream.close();
        }
    }finally {
        inputStream.close();
    }

在這種方式中即使是outStream.close()拋出了異常,可是咱們依然會執行到inputStream.close(),由於他們是在不一樣的finally塊,這個的確解決了咱們的問題,可是還有兩個問題沒有解決:code

  • 帶來的第一個問題就是若是咱們有不止兩個資源,好比有十個資源,難道須要讓咱們寫十個嵌套的語句嗎?寫完以後這個代碼還能看嗎?
  • 第二個問題就是若是咱們在try裏面出現異常,而後在finally裏面又出現異常,就會致使異常覆蓋,會致使finally裏面的異常將try的異常覆蓋了。
public class CloseTest {

    public void close(){
        throw new RuntimeException("close");
    }

    public static void main(String[] args) {
        CloseTest closeTest = new CloseTest();
        try{
            throw new RuntimeException("doSomething");
        }finally {
            closeTest.close();
        }
    }

}
輸出結果:Exception in thread "main" java.lang.RuntimeException: close

上面這個代碼,咱們指望的是能拋出doSomething的這個異常,可是實際的數據結果倒是close的異常,這和咱們的預期不符合。blog

try-with-resources

上面咱們介紹了兩個問題,因而在java7中引入了try-with-resources的語句,只要咱們的資源實現了AutoCloseable這個接口那麼咱們就可使用這個語句了,咱們以前的文件流已經實現了這個接口那麼咱們能夠直接使用:接口

try(InputStream inputStream = new FileInputStream("file");
            OutputStream outStream = new FileOutputStream("file1")) {
            System.out.println(inputStream.read(new byte[4]));
            outStream.write(new byte[4]);
        }

咱們全部的資源定義所有都在try後面的括號中進行定義,經過這種方式咱們就能夠解決上面所說的幾個問題:資源

  • 首先第一個問題,咱們經過這樣的方式,代碼很是整潔,不管你有多少個資源,均可以很簡潔的去作。
  • 第二個異常覆蓋問題的話,咱們能夠經過實驗來看一下,咱們將代碼改寫爲以下:
public class CloseTest implements AutoCloseable {

    @Override
    public void close(){
        System.out.println("close");
        throw new RuntimeException("close");
    }

    public static void main(String[] args) {
        try(CloseTest closeTest = new CloseTest();
            CloseTest closeTest1 = new CloseTest();){
            throw new RuntimeException("Something");
        }
    }

}
輸出結果爲:
close
close
Exception in thread "main" java.lang.RuntimeException: Something
	at fudao.CloseTest.main(CloseTest.java:33)
	Suppressed: java.lang.RuntimeException: close
		at fudao.CloseTest.close(CloseTest.java:26)
		at fudao.CloseTest.main(CloseTest.java:34)
	Suppressed: java.lang.RuntimeException: close
		at fudao.CloseTest.close(CloseTest.java:26)
		at fudao.CloseTest.main(CloseTest.java:34)

咱們在代碼中定義了兩個CloseTest,用來驗證以前close出現異常是否會影響第二個,同時在close和try塊裏面都拋出不一樣的異常,能夠看見咱們的結果,輸出了兩個close,證實雖然close拋出異常,可是兩個close都會執行。而後輸出了doSomething的異常,能夠發現這裏咱們輸出的就是咱們try塊裏面所拋出的異常,而且咱們close的異常以Suppressed的方式記錄在異常的堆棧裏面,經過這樣的方式咱們兩種異常都能記錄下來。input

try-with-resources原理

try-with-resources語句實際上是一種語法糖,經過編譯以後又回到了咱們開始說的嵌套的那種模式: it

能夠發現try-with-resources被編譯以後,又採起了嵌套的模式,可是和以前的嵌套有點不一樣,他close的時候都利用了catch去捕獲了異常,而後添加到咱們真正的異常中,總體邏輯比咱們以前的嵌套要複雜一些。

總結

在咱們關閉資源的時候,咱們儘可能優先推薦使用try-with-resources語句,但這裏要注意的是不少資源實際上是沒有實現AutoCloseable接口的,好比咱們最開始的Lock就沒有實現這個接口,這個時候若是也想使用這種功能,能夠採用組合的方式將Lock進行封裝,來達到咱們的目的。

若是你們以爲這篇文章對你有幫助,你的關注和轉發是對我最大的支持,O(∩_∩)O:

相關文章
相關標籤/搜索