二哥疾棵,你之前那篇 我去 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)師的面試視頻哦。絕對不容錯過扣唱,期待與你的不期而遇藕坯。