try-catch-finally 和 try-with-resources

一、簡(jiǎn)述

如果在 try 語(yǔ)句塊里使用 return 語(yǔ)句,那么 finally 語(yǔ)句塊還會(huì)執(zhí)行嗎?答案是肯定的。Java 官方文檔上是這么描述的:The finally block always executes when the try block exits.。描述詞用的是 always,即在 try 執(zhí)行完成之后,finally 是一定會(huì)執(zhí)行的。這種特性可以讓程序員避免在 try 語(yǔ)句中使用了return、continue 或者 break 關(guān)鍵字而忽略了關(guān)閉相關(guān)資源的操作。把清理相關(guān)資源放到 finally 語(yǔ)句塊中一直是最佳實(shí)踐。

ps:用到 finally 關(guān)閉資源的時(shí)候,應(yīng)該盡量避免在 finally 語(yǔ)句塊中出現(xiàn)運(yùn)行時(shí)錯(cuò)誤,可以適當(dāng)添加判斷語(yǔ)句以增加程序健壯性:

finally {
  if (out != null) { 
     System.out.println("Closing PrintWriter");
     out.close(); // 不要在finally語(yǔ)句中直接調(diào)用close()
   } else { 
     System.out.println("PrintWriter not open");
   } 
}
  1. 不管有木有出現(xiàn)異常,finally 塊中代碼都會(huì)執(zhí)行。
  2. 當(dāng) try 和 catch 中有 return 時(shí),finally 仍然會(huì)執(zhí)行。
  3. finally 是在 return 后面的表達(dá)式運(yùn)算后執(zhí)行的(此時(shí)并沒(méi)有返回運(yùn)算后的值,而是先把要返回的值保存起來(lái)【先存到一個(gè)本地變量中】,不管 finally 中的代碼怎么樣,返回的值都不會(huì)改變,仍然是之前保存的值),所以函數(shù)返回值是在 finally 執(zhí)行前確定的。
  4. finally 中最好不要包含 return,否則程序會(huì)提前退出,返回值不是 try 或 catch 中保存的返回值。因?yàn)橐?guī)范規(guī)定了,當(dāng) try 和 finally 里都有 return 時(shí),會(huì)忽略 try 的 return,而使用 finally 的 return。

二、應(yīng)用場(chǎng)景

1??try{} catch(){}finally{} return;
顯然程序按順序執(zhí)行。
2??try{ return; }catch(){} finally{} return;
程序執(zhí)行 try 塊中 return 之前(包括 return 語(yǔ)句中的表達(dá)式運(yùn)算)代碼;
再執(zhí)行 finally 塊,最后執(zhí)行 try 中 return;
finally 塊之后的語(yǔ)句 return,因?yàn)槌绦蛟?try 中已經(jīng) return 所以不再執(zhí)行。
3??try{ } catch(){return;} finally{} return;
程序先執(zhí)行 try,如果遇到異常執(zhí)行 catch 塊,
有異常:則執(zhí)行 catch 中 return 之前(包括 return 語(yǔ)句中的表達(dá)式運(yùn)算)代碼,再執(zhí)行 finally 語(yǔ)句中全部代碼,最后執(zhí)行 catch 塊中 return。finally 之后也就是4處的代碼不再執(zhí)行。
無(wú)異常:執(zhí)行完 try 再 finally 再 return。
4??try{ return; }catch(){} finally{return;}
程序執(zhí)行 try 塊中 return 之前(包括 return 語(yǔ)句中的表達(dá)式運(yùn)算)代碼;
再執(zhí)行 finally 塊,因?yàn)?finally 塊中有 return 所以提前退出。
5??try{} catch(){return;}finally{return;}
程序執(zhí)行 catch 塊中 return 之前(包括 return 語(yǔ)句中的表達(dá)式運(yùn)算)代碼;
再執(zhí)行 finally 塊,因?yàn)?finally 塊中有 return 所以提前退出。
6??try{ return;}catch(){return;} finally{return;}
程序執(zhí)行 try 塊中 return 之前(包括 return 語(yǔ)句中的表達(dá)式運(yùn)算)代碼;
有異常:執(zhí)行 catch 塊中 return 之前(包括 return 語(yǔ)句中的表達(dá)式運(yùn)算)代碼;則再執(zhí)行 finally 塊,因?yàn)?finally 塊中有 return 所以提前退出。
無(wú)異常:則再執(zhí)行 finally 塊,因?yàn)?finally 塊中有 return 所以提前退出。

三、小結(jié)

1??如果 finally 存在的話,任何執(zhí)行 try 或者 catch 中的 return 語(yǔ)句之前,都會(huì)先執(zhí)行 finally 語(yǔ)句。
2??如果 finally 中有 return 語(yǔ)句,那么程序就 return 了。所以 finally 中的 return 是一定會(huì)被 return 的,編譯器把 finally 中的 return 實(shí)現(xiàn)為一個(gè) warning。

public class FinallyTest {
public static void main(String[] args) {
    System.out.println(new FinallyTest().test());;
}
static int test(){
    int x = 1;
    try{
        x++;
        return x;
    }finally{
        ++x;
        }
      }
  }

結(jié)果是2。
分析:在 try 語(yǔ)句中,在執(zhí)行 return 語(yǔ)句時(shí),要返回的結(jié)果已經(jīng)準(zhǔn)備好了,就在此時(shí),程序轉(zhuǎn)到 finally 執(zhí)行了。在轉(zhuǎn)去之前,try 中先把要返回的結(jié)果存放到不同于 x 的【局部變量】中去,執(zhí)行完 finally 之后,在從中取出返回結(jié)果。因此,即使 finally 中對(duì)變量 x 進(jìn)行了改變,但是不會(huì)影響返回結(jié)果。它應(yīng)該使用棧保存返回值。

四、自動(dòng)資源管理(Automatic Resource Management)

在 JDK1.7 前,通常使用 try{}catch() 來(lái)捕獲異常的,如果遇到類似 IO 流的處理,一般是在 finally 部分關(guān)閉 IO 流。但在 JDK1.7 后,Java 7 的編譯器和運(yùn)行環(huán)境支持新的 try-with-resources 語(yǔ)句,稱為 ARM 塊。寫在()里面的流對(duì)象對(duì)應(yīng)的類都實(shí)現(xiàn)了自動(dòng)關(guān)閉接口 AutoCloseable。

1??認(rèn)識(shí)java.lang.AutoCloseable接口

JDK1.7 之前,通過(guò) try{} finally{} 在 finally 中釋放資源。在 finally 中關(guān)閉資源存在以下問(wèn)題:

  1. 開發(fā)者需要手動(dòng)寫代碼做關(guān)閉的邏輯。
  2. 有時(shí)候可能會(huì)忘記關(guān)閉一些資源,導(dǎo)致內(nèi)存泄漏。
  3. 關(guān)閉代碼的邏輯比較冗長(zhǎng),代碼可讀性差。

在 JDK1.7 后,對(duì)于實(shí)現(xiàn) AutoCloseable 接口的類的實(shí)例,將其放到 try 后面(稱之為:帶資源的 try 語(yǔ)句),在 try 結(jié)束的時(shí)候,會(huì)自動(dòng)將這些資源關(guān)閉(調(diào)用 close 方法)。帶資源的 try 語(yǔ)句的 3 個(gè)關(guān)鍵點(diǎn):

  1. 由帶資源的 try 語(yǔ)句管理的資源必須是實(shí)現(xiàn)了 AutoCloseable 接口的類的對(duì)象。
  2. 在 try 代碼中聲明的資源被隱式聲明為 final
  3. 通過(guò)使用分號(hào)分隔每個(gè)聲明可以管理多個(gè)資源。

格式:

try (創(chuàng)建流對(duì)象語(yǔ)句,如果多個(gè)可以使用';'隔開) {
    // dosomething
} catch (IOException e) {
    e.printStackTrace();
}

2??JDK1.7 前

public static void writeFile(String str) {
    FileWriter fw = null;
    try {
        fw = new FileWriter("e://wg.txt", true);
        fw.write(str);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (fw != null) {
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3??JDK1.7 后
資源關(guān)閉不需要寫代碼操作。這些可關(guān)閉的資源必須實(shí)現(xiàn)java.lang.AutoCloseable接口。比如將 FileWriter 放置 try() 中:

public static void writeFile(String str) {
    try (FileWriter fw = new FileWriter("e://wg.txt", true)) {
        fw.write(str);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

FileWriter 繼承了 OutputStreamWriter。OutputStreamWriter 繼承了 Writer。Writer 實(shí)現(xiàn)了 Closeable。Closeable 繼承了 AutoCloseable。滿足了帶資源關(guān)閉的基本要求。當(dāng) try 中的{}執(zhí)行完后,將會(huì)自動(dòng)關(guān)閉資源。

4???理解

public class Demo {
    public static void main(String[] args) {
        try (MyResource resource = new MyResource();) {
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class MyResource implements AutoCloseable {
    @Override
    public void close() throws Exception {
        System.out.println("關(guān)閉自定義資源");
    }
}

結(jié)果如下:

關(guān)閉自定義資源

在 try () 中僅僅 new 了一個(gè) MyResource 的對(duì)象,但 close() 中的輸出語(yǔ)句執(zhí)行了。看下反編譯后的字節(jié)碼:

class MyResource implements AutoCloseable {
    MyResource() {
    }
    public void close() throws Exception {
        System.out.println("關(guān)閉自定義資源");
    }
}

public class Demo {
    public Demo () {
    }
    public static void main(String[] args) {
        try {
            MyResource resource = new MyResource();
            resource.close();
        } catch (Exception var2) {
            var2.printStackTrace();
        }
    }
}

編譯器竟然主動(dòng)為 try-with-resources 進(jìn)行了變身,在 try 中調(diào)用了 close()。在自定義類中再添加一個(gè) out():

class MyResourceOut implements AutoCloseable {
    @Override
    public void close() throws Exception {
        System.out.println("關(guān)閉自定義資源");
    }
    public void out() throws Exception{
        System.out.println("hello,out");
    }
}

在 try{} 中調(diào)用一下 out():

public class DemoOut {
    public static void main(String[] args) {
        try (MyResourceOut resource = new MyResourceOut();) {
            resource.out();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

再來(lái)看一下反編譯的字節(jié)碼:

public class DemoOut {
    public DemoOut () {
    }

    public static void main(String[] args) {
        try {
            MyResourceOut resource = new MyResourceOut();
            try {
                resource.out();
            } catch (Throwable var5) {
                try {
                    resource.close();
                } catch (Throwable var4) {
                    var5.addSuppressed(var4);
                }
                throw var5;
            }
            resource.close();
        } catch (Exception var6) {
            var6.printStackTrace();
        }
    }
}

這次,catch 塊中主動(dòng)調(diào)用了 resource.close(),并且有一段很關(guān)鍵的代碼 var5.addSuppressed(var4)。它有什么用處呢?當(dāng)一個(gè)異常被拋出的時(shí)候,可能有其他異常因?yàn)樵摦惓6灰种谱?,從而無(wú)法正常拋出。這時(shí)可以通過(guò) addSuppressed() 方法把這些被抑制的方法記錄下來(lái)。被抑制的異常會(huì)出現(xiàn)在拋出的異常的堆棧信息中,也可以通過(guò) getSuppressed() 來(lái)獲取這些異常。這樣做的好處是不會(huì)丟失任何異常,方便調(diào)試。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 出發(fā)在路上 行進(jìn)在路上 歸來(lái)在路上 我是個(gè)行者 永無(wú)止境
    丁生火閱讀 267評(píng)論 0 3
  • 0. 本文部分參考自https://blog.csdn.net/github_35041937/article/d...
    ZTRogers閱讀 3,532評(píng)論 3 2
  • 成長(zhǎng)不是什么偶然的事,畢幾次業(yè),就會(huì)意識(shí)到自己不再年輕。還沒(méi)理解什么叫長(zhǎng)大,什么叫成熟,轉(zhuǎn)眼發(fā)現(xiàn),自己已經(jīng)行到一個(gè)...
    凸樹閱讀 124評(píng)論 0 2