我去炸客,你竟然還在用 try–catch-finally

二哥疾棵,你之前那篇 我去 switch 的文章也特么太有趣了,讀完后意猶未盡啊痹仙,要不要再寫一篇笆嵌?雖然用的是 Java 13 的語法开仰,對舊版本不太友好拟枚。但誰能保證 Java 不會再來一次重大更新呢薪铜,就像 Java 8 那樣,活生生地把 Java 6 拍死在了沙灘上恩溅。Java 8 是香隔箍,但早晚要升級,我挺你脚乡,二哥蜒滩,別在乎那些反對的聲音。

這是讀者 Alice 上周特意給我發(fā)來的信息奶稠,真令我動容俯艰。的確,上次的“我去”閱讀量杠杠的锌订,幾個大號都轉(zhuǎn)載了竹握,包括 CSDN,次條當(dāng)天都 1.5 萬閱讀辆飘。但比如“還以為你有什么新特技啦辐,沒想到用的是 Java 13”這類批評的聲音也不在少數(shù)。

不過我的心一直很大劈猪。從我寫第一篇文章至今昧甘,被噴的次數(shù)就好像頭頂上茂密的發(fā)量一樣,數(shù)也數(shù)不清战得。所以我決定再接再厲,帶來新的一篇“我去”庸推。

這次不用遠(yuǎn)程 review 了常侦,因?yàn)槲覀児疽矎?fù)工了。這次 review 的代碼仍然是小王的贬媒,他編寫的大部分代碼都很漂亮聋亡,嚴(yán)謹(jǐn)?shù)耐瑫r注釋也很到位,這令我非常滿意际乘。但當(dāng)我看到他沒用 try-with-resources 時坡倔,還是忍不住破口大罵:“我擦,小王脖含,你丫的竟然還在用 try–catch-finally罪塔!”

來看看小王寫的代碼吧。

public class Trycatchfinally {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader("/牛逼.txt"));
            String str = null;
            while ((str =br.readLine()) != null) {
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

咦养葵,感覺這段代碼很完美無缺啊征堪,try–catch-finally 用得中規(guī)中矩,尤其是文件名 牛逼.txt 很亮关拒。不用寫注釋都能明白這段代碼是干嘛的:在 try 塊中讀取文件中的內(nèi)容佃蚜,并一行一行地打印到控制臺庸娱。如果文件找不到或者出現(xiàn) IO 讀寫錯誤,就在 catch 中捕獲并打印錯誤的堆棧信息谐算。最后熟尉,在 finally 中關(guān)閉緩沖字符讀取器對象 BufferedReader,有效杜絕了資源未被關(guān)閉的情況下造成的嚴(yán)重性能后果洲脂。

在 Java 7 之前臣樱,try–catch-finally 的確是確保資源會被及時關(guān)閉的最佳方法,無論程序是否會拋出異常腮考。

但是呢雇毫,有經(jīng)驗(yàn)的讀者會從上面這段代碼中發(fā)現(xiàn) 2 個嚴(yán)重的問題:

1)文件名“牛逼.txt”包含了中文,需要通過 java.net.URLDecoder 類的 decode() 方法對其轉(zhuǎn)義踩蔚,否則這段代碼在運(yùn)行時鐵定要拋出文件找不到的異常棚放。

2)如果直接通過 new FileReader("牛逼.txt") 創(chuàng)建 FileReader 對象,“牛逼.txt”需要和項(xiàng)目的 src 在同一級目錄下馅闽,否則同樣會拋出文件找不到的異常飘蚯。但大多數(shù)情況下,(配置)文件會放在 resources 目錄下福也,便于編譯后文件出現(xiàn)在 classes 目錄下局骤,見下圖。

為了解決以上 2 個問題暴凑,我們需要對代碼進(jìn)行優(yōu)化:

public class TrycatchfinallyDecoder {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            String path = TrycatchfinallyDecoder.class.getResource("/牛逼.txt").getFile();
            String decodePath = URLDecoder.decode(path,"utf-8");
            br = new BufferedReader(new FileReader(decodePath));

            String str = null;
            while ((str =br.readLine()) != null) {
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

運(yùn)行這段代碼峦甩,程序就可以將文件中的內(nèi)容正確輸出到控制臺。但如果你對“整潔”這個詞心生向往的話现喳,會感覺這段代碼非常臃腫凯傲,尤其是 finally 中的代碼,就好像一個灌了 12 瓶雪花啤酒的大肚腩嗦篱。

網(wǎng)上看到一幅 Python 程序員調(diào)侃 Java 程序員的神圖冰单,直接 copy 過來(侵刪),逗你一樂:

況且灸促,try–catch-finally 至始至終存在一個嚴(yán)重的隱患:try 中的 br.readLine() 有可能會拋出 IOException诫欠,finally 中的 br.close() 也有可能會拋出 IOException。假如兩處都不幸地拋出了 IOException浴栽,那程序的調(diào)試任務(wù)就變得復(fù)雜了起來荒叼,到底是哪一處出了錯誤,就需要花一番功夫吃度,這是我們不愿意看到的結(jié)果甩挫。

為了模擬上述情況,我們來自定義一個類 MyfinallyReadLineThrow椿每,它有兩個方法伊者,分別是 readLine()close()英遭,方法體都是主動拋出異常。

class MyfinallyReadLineThrow {
    public void close() throws Exception {
        throw new Exception("close");
    }

    public void readLine() throws Exception {
        throw new Exception("readLine");
    }
}

然后我們在 main() 方法中使用 try-finally 的方式調(diào)用 MyfinallyReadLineThrow 的 readLine()close() 方法:

public class TryfinallyCustomReadLineThrow {
    public static void main(String[] args) throws Exception {
        MyfinallyReadLineThrow myThrow = null;
        try {
            myThrow = new MyfinallyReadLineThrow();
            myThrow.readLine();
        } finally {
            myThrow.close();
        }
    }
}

運(yùn)行上述代碼后亦渗,錯誤堆棧如下所示:

Exception in thread "main" java.lang.Exception: close
    at com.cmower.dzone.trycatchfinally.MyfinallyOutThrow.close(TryfinallyCustomOutThrow.java:17)
    at com.cmower.dzone.trycatchfinally.TryfinallyCustomOutThrow.main(TryfinallyCustomOutThrow.java:10)

readLine() 方法的異常信息竟然被 close() 方法的堆棧信息吃了挖诸,這必然會讓我們誤以為要調(diào)查的目標(biāo)是 close() 方法而不是 readLine()——盡管它也是應(yīng)該懷疑的對象。

但自從有了 try-with-resources法精,這些問題就迎刃而解了多律,只要需要釋放的資源(比如 BufferedReader)實(shí)現(xiàn)了 AutoCloseable 接口。有了解決方案之后搂蜓,我們來對之前的 finally 代碼塊進(jìn)行瘦身狼荞。

try (BufferedReader br = new BufferedReader(new FileReader(decodePath));) {
    String str = null;
    while ((str =br.readLine()) != null) {
        System.out.println(str);
    }
} catch (IOException e) {
    e.printStackTrace();
}

你瞧,finally 代碼塊消失了帮碰,取而代之的是把要釋放的資源寫在 try 后的 () 中相味。如果有多個資源(BufferedReader 和 PrintWriter)需要釋放的話,可以直接在 () 中添加殉挽。

try (BufferedReader br = new BufferedReader(new FileReader(decodePath));
     PrintWriter writer = new PrintWriter(new File(writePath))) {
    String str = null;
    while ((str =br.readLine()) != null) {
        writer.print(str);
    }
} catch (IOException e) {
    e.printStackTrace();
}

如果你想釋放自定義資源的話丰涉,只要讓它實(shí)現(xiàn) AutoCloseable 接口,并提供 close() 方法即可斯碌。

public class TrywithresourcesCustom {
    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)閉自定義資源");
    }
}

代碼運(yùn)行后輸出的結(jié)果如下所示:

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

是不是很神奇一死?我們在 try () 中只是 new 了一個 MyResource 的對象,其他什么也沒干傻唾,但偏偏 close() 方法中的輸出語句執(zhí)行了投慈。想要知道為什么嗎?來看看反編譯后的字節(jié)碼吧策吠。

class MyResource implements AutoCloseable {
    MyResource() {
    }

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

public class TrywithresourcesCustom {
    public TrywithresourcesCustom() {
    }

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

    }
}

咦逛裤,編譯器竟然主動為 try-with-resources 進(jìn)行了變身,在 try 中調(diào)用了 close() 方法猴抹。

接下來,我們在自定義類中再添加一個 out() 方法锁荔,

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

    public void out() throws Exception{
        System.out.println("沉默王二蟀给,一枚有趣的程序員");
    }
}

這次,我們在 try 中調(diào)用一下 out() 方法:

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

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

public class TrywithresourcesCustomOut {
    public TrywithresourcesCustomOut() {
    }

    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 塊中主動調(diào)用了 resource.close()跋理,并且有一段很關(guān)鍵的代碼 var5.addSuppressed(var4)。它有什么用處呢恬总?當(dāng)一個異常被拋出的時候前普,可能有其他異常因?yàn)樵摦惓6灰种谱。瑥亩鵁o法正常拋出壹堰。這時可以通過 addSuppressed() 方法把這些被抑制的方法記錄下來拭卿。被抑制的異常會出現(xiàn)在拋出的異常的堆棧信息中骡湖,也可以通過 getSuppressed() 方法來獲取這些異常。這樣做的好處是不會丟失任何異常峻厚,方便我們開發(fā)人員進(jìn)行調(diào)試响蕴。

哇,有沒有想到我們之前的那個例子——在 try-finally 中惠桃,readLine() 方法的異常信息竟然被 close() 方法的堆棧信息吃了∑忠模現(xiàn)在有了 try-with-resources,再來看看作用和 readLine() 方法一致的 out() 方法會不會被 close() 吃掉辜王。

close()out() 方法中直接拋出異常:

class MyResourceOutThrow implements AutoCloseable {
    @Override
    public void close() throws Exception {
        throw  new Exception("close()");
    }

    public void out() throws Exception{
        throw new Exception("out()");
    }
}

調(diào)用這 2 個方法:

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

程序輸出的結(jié)果如下所示:

java.lang.Exception: out()
    at com.cmower.dzone.trycatchfinally.MyResourceOutThrow.out(TrywithresourcesCustomOutThrow.java:20)
    at com.cmower.dzone.trycatchfinally.TrywithresourcesCustomOutThrow.main(TrywithresourcesCustomOutThrow.java:6)
    Suppressed: java.lang.Exception: close()
        at com.cmower.dzone.trycatchfinally.MyResourceOutThrow.close(TrywithresourcesCustomOutThrow.java:16)
        at com.cmower.dzone.trycatchfinally.TrywithresourcesCustomOutThrow.main(TrywithresourcesCustomOutThrow.java:5)

瞧劈狐,這次不會了,out() 的異常堆棧信息打印出來了呐馆,并且 close() 方法的堆棧信息上加了一個關(guān)鍵字 Suppressed肥缔。一目了然,不錯不錯摹恰,我喜歡辫继。

總結(jié)一下,在處理必須關(guān)閉的資源時俗慈,始終有限考慮使用 try-with-resources姑宽,而不是 try–catch-finally。前者產(chǎn)生的代碼更加簡潔闺阱、清晰炮车,產(chǎn)生的異常信息也更靠譜。答應(yīng)我好不好酣溃?別再用 try–catch-finally 了瘦穆。

覺得有點(diǎn)用記得給我點(diǎn)贊哦!??

簡單介紹一下赊豌。10 年前扛或,當(dāng)我上大學(xué)的時候,專業(yè)被調(diào)劑到了計(jì)算機(jī)網(wǎng)絡(luò)碘饼,主要學(xué)的是 Java 編程語言熙兔,但當(dāng)時沒怎么好好學(xué),每年都要掛科兩三門艾恼;因此工作后吃了不少虧住涉。但是最近幾年,情況發(fā)生了很大改變钠绍,你應(yīng)該也能看得到我這種變化舆声。通過堅(jiān)持不懈地學(xué)習(xí),持續(xù)不斷地輸出柳爽,我的編程基本功算得上是突飛猛進(jìn)媳握。

為了幫助更多的程序員碱屁,我創(chuàng)建了“沉默王二”這個 ID,專注于分享有趣的 Java 技術(shù)編程和有益的程序人生毙芜。一開始忽媒,閱讀量寥寥無幾,關(guān)注人數(shù)更是少得可憐腋粥。但隨著影響力的逐步擴(kuò)大晦雨,閱讀量和關(guān)注人都在猛烈攀升。

你在看這篇文章的時候隘冲,應(yīng)該也能發(fā)現(xiàn)闹瞧,我在 CSDN 上的總排名已經(jīng)來到了第 71 位,這個排名還是非常給力的展辞。有很多讀者都說奥邮,我可以沖擊第一名,我不愿意藏著掖著罗珍,我是有這個野心的洽腺。如果你也喜歡我的文章,請記得微信搜索「沉默王二」關(guān)注我的原創(chuàng)公眾號覆旱,回復(fù)“1024”更有美團(tuán)技術(shù)大佬整理的 Java 面試攻略相送蘸朋,還有架構(gòu)師的面試視頻哦。絕對不容錯過扣唱,期待與你的不期而遇藕坯。

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市噪沙,隨后出現(xiàn)的幾起案子炼彪,更是在濱河造成了極大的恐慌,老刑警劉巖正歼,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辐马,死亡現(xiàn)場離奇詭異,居然都是意外死亡局义,警方通過查閱死者的電腦和手機(jī)齐疙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旭咽,“玉大人,你說我怎么就攤上這事赌厅∏蠲啵” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵特愿,是天一觀的道長勾缭。 經(jīng)常有香客問我,道長目养,這世上最難降的妖魔是什么俩由? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮癌蚁,結(jié)果婚禮上幻梯,老公的妹妹穿的比我還像新娘。我一直安慰自己努释,他們只是感情好碘梢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著伐蒂,像睡著了一般煞躬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逸邦,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天恩沛,我揣著相機(jī)與錄音,去河邊找鬼缕减。 笑死雷客,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的烛卧。 我是一名探鬼主播佛纫,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼总放!你這毒婦竟也來了呈宇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤局雄,失蹤者是張志新(化名)和其女友劉穎甥啄,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炬搭,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜈漓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了宫盔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片融虽。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖灼芭,靈堂內(nèi)的尸體忽然破棺而出有额,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布巍佑,位于F島的核電站茴迁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏萤衰。R本人自食惡果不足惜堕义,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脆栋。 院中可真熱鬧倦卖,春花似錦、人聲如沸筹吐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丘薛。三九已至嘉竟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間洋侨,已是汗流浹背舍扰。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留希坚,地道東北人边苹。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像裁僧,于是被迫代替她去往敵國和親个束。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

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