前言: 通常我們程式執行正常的時候,一切都很美好。但是月有陰晴圓缺,往往都會有意外發生。這篇就是來分享一下遇到意外時,該如何處理 ~
像是下面這張圖,相信大家很常見到 ~ 就是一個常見的情境 ~
在開始進入正題(Exception Handling)之前 ,先說明一下 Java 跟 Exception 關係 ~
(* Exception 跟 Error 以及 RuntimeException的繼承關係圖)
https://docs.oracle.com/javase/tutorial/figures/essential/exceptions-throwable.gif
程式裏面,如果是遇到 Error/RuntimeException ,這一類的類別被Java歸類成 UnChecked Exception,代表 程式人員 不用特別寫程式進行處理。(*因為這一類的情形,如果真有發生,我們也來不及救了… 。如果JVM 出現OutMemoryError,我們也只能舉雙手投降了)。
至於像其它常見的,像 IOException,FileNotFoundException 等,
都分類為 Checked Exception,這些都是代表程式出錯時,JVM 會把 出錯的資訊 包裝成特定型態的物件,方便程式人員可以用程式來挽救出錯的情形,但不致於讓程式整個直接結束( Game Over)。
Java對所謂的 Checked Exception,在語法上有提供了幾個相關的關鍵字,
如 try / catch 以及 throws。接下來,我們先來看 用 try / catch的用法 ~
Round 1 : 通常程式需要 I/O, connect Network, 或 Database 時,我們都會加上 try/catch
如下
|
public void doNotCloseResourceInTry() { FileInputStream inputStream = null; try { File file = new File("./tmp.txt"); inputStream = new FileInputStream(file); //<span style="color: #ff0000;">如果這行出錯,下面還會執行嗎</span> ?? inputStream.close(); // 可能會被跳過 !!! } catch (FileNotFoundException e) { log.error(e); } catch (IOException e) { log.error(e); } |
上面的寫法,相信很容易在各種案子或各個地方看到~
基本上是ok的…javac 編譯也會過…我們稱 「有借有還」
就像 有 open動作,就該會有 close的動作。
不過,重點 不只要寫 close,還要在對的地方寫 ,寫錯地方 就等同於沒寫。
在上面第五行的地方,使用了 new FileInputStream(file) ;
也許各位會想,就是 new FileInputStream的物件,為何會出錯呢 ?!
原因可能會有很多,舉簡單二、三個原因 來看,
- 檔案已經存在。代表無法再產生一個 FileInputStream來存取同一份檔案資源
- 寫入權限不足,比如說 一般user無法寫入系統的資料夾
- 硬碟空間已滿,這理由也很有可能會發生的喔 ~~~
那出錯之後呢 ?! 程式沒出錯的話,會一路從上往下執行,然後略過 catch。
但是如果一出錯,則程式流程會立即跳出 try { } 的區塊,然後進入 catch 的部份。
以上面來說的話,就是第五行出錯,會直接跳到第七行的 catch ,
而忽略最重要的第六行 close() . (*進而可能會導致記憶體一直浪費,而沒有被回收,也就是所謂的 Memory Leak)
所以,該怎麼做比較對呢 ?!
Round 2 : 加上 finally ,如下面的第九行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public void closeResourceInFinally() { FileInputStream inputStream = null; try { File file = new File("./tmp.txt"); inputStream = new FileInputStream(file); // use the inputStream to read a file } catch (FileNotFoundException e) { log.error(e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { log.error(e); } } } } |
但是別忘了,即使在第九行之後,如果有呼叫 close() 時,也還是要再加一個 try/catch 才能真正的讓編譯器通過。 (*不然就是考慮 throws 用法 ,我們稱之謂 「轉身逃避」)
但…又有人會說,為何已經用 try / catch 了,而裏面還要再包一層呢 ~~
是不是 感覺有點多餘呢 ~?!
沒錯,就是有點多餘… 所以在 JDK 7 出來後,Oracle 原廠有加入個簡化的語法,
稱之為 try-with-resource,
語法:
try ( 欲使用後關閉的物件 ) {
// 正常地 使用物件
} catch (相對應的例外) {
// 例外發生時的處理,如 log, rollback 或 send email 等
}
範例如下 :
|
public void automaticallyCloseResource() { File file = new File("./tmp.txt"); try (FileInputStream inputStream = new FileInputStream(file);) { // use the inputStream to read a file } catch (FileNotFoundException e) { log.error(e); } catch (IOException e) { log.error(e); } } |
不知各位有沒有注意到,
- 沒有 close()
- 更沒有巢狀的 try / catch
try-with-resource的語法就是要讓開發人員更簡便的開發程式,
這麼好用的語法有沒有限制呢 ?! 答案是 有 !!!!
限制就是 欲使用後關閉的物件 必需直接或間接地 實作 AutoCloseable ,至於有哪些類別已經實作,請參考下圖
如果 使用的物件不符合期待(也就是直接/間接地 實作 AutoCloseable) ,
編譯器會”好心的提醒”開發人員…
是不是真的很實用呢 ?!
也許會想問說,如果是用 JDK6 甚至是 JDK5 的話呢 ?! 嗯~~ 沒問題的,就像下面這張~
(* 就別做夢了,趕快昇級 JDK 版本囉 ~ XD
後半部,我們會再接著介紹程式出錯之後, Exception 的建議處理方式 ~
參考資料(Reference) :
https://dzone.com/articles/9-best-practices-to-handle-exceptions-in-java