Java異常捕捉陷阱(記憶體洩漏,finally塊,catch塊,繼承得到的異常)
1. 異常捕捉的陷阱
異常處理機制是java語言的特色之一,尤其是java語言的Checked異常,更是體現了java語言的嚴謹性:沒有完善錯誤處理的程式碼根本不會被執行。對於Checked異常,java程式要麼宣告丟擲,要麼使用try……catch進行捕獲。
1.1 正確關閉資源的方式
在實際開發中,經常需要在程式中開啟一些物理資源,如資料庫連線,網路連線,磁碟檔案等,開啟這些物理資源之後必須顯示關閉,否則將會導致資源洩漏。因為垃圾回收機制屬於java記憶體管理的一部分,它只是負責會受堆記憶體中分配出來的記憶體,至於程式中開啟的物理資源,垃圾回收機制是無能為力的。
為了正常關閉程式中開啟的物理資源,應該使用finally塊來保證回收。比如下面三種關閉資源的方式,哪種更好些?
- public static void main(String args[]) throws Exception {
- Student student_new = new Student("liyafang");
- Student student_recover = null;
- ObjectOutputStream oos = null;
- ObjectInputStream ois = null;
- try {
- oos = new ObjectOutputStream(new FileOutputStream("liyafang.txt"));
- ois = new ObjectInputStream(new FileInputStream("liyafang.txt"));
- oos.writeObject(student_new);
- oos.flush();
- student_recover = (Student) ois.readObject();
- System.out.println(student_recover.equals(student_new));
- System.out.println(student_recover == student_new);
- } finally {
- //1.第一種關閉資源的方式(不夠安全):程式剛開始指定oos = null;ois = null;完全有可能在程式執行過程中初始化oos之前就引發了異常,那麼oos,ois還沒有來得及初始化,因此oos,ois根本無需關閉。
- oos.close();
- ois.close();
- //2.第二種關閉資源的方式(還是不夠安全):假如程式開始已經正常初始化了oos,ois兩個IO流,在關閉oos是出現了異常,那麼程式將在關閉oos時非正常退出,這樣就導致ois得不到關閉,從而導致資源洩漏。為了保證關閉各資源時出現的異常不會相互影響,應該在關閉每個資源時分開使用try catch塊來保證關閉操作不會導致程式非正常退出。
- if(oos != null){
- oos.close();
- }
- if(ois != null){
- ois.close();
- }
- //3.第三種關閉資源的方式(比較安全):主要保證一下幾點:
- //(1)使用finally塊來關閉物理資源,保證關閉操作始終會被執行;
- //(2)關閉每個資源之前首先保證引用該資源的引用變數不為null;
- //(3)為每個物理資源使用單獨的trycatch塊關閉資源,保證關閉資源時引發的異常不會影響其他資源的關閉。
- if(oos != null){
- try{
- oos.close();
- }catch (Exception ex){
- ex.printStackTrace();
- }
- }
- if(ois != null){
- try{
- ois.close();
- }catch (Exception ex){
- ex.printStackTrace();
- }
- } }
- }
1.2 finally塊的陷阱
當程式在finally之前使用System.exit(0),finally將不執行。呼叫System.exit(0)將使JVM退出,只要JVM不退出,finally就一定會得到執行。
在java程式執行try塊、catch塊時遇到了return語句,return語句會導致該方法立即結束。系統執行完return語句之後,並不會立即結束該方法,而是去尋找該異常處理流程中是否包含Finally塊,如果沒有Finally塊,方法終止,返回相應的返回值。如果有Finally塊,系統立即開始執行Finally塊,只有當Finally執行完成後,系統才會再次跳回來根據return語句結束方法。如果Finally塊使用了return語句來導致方法的結束,則finally塊已經結束了方法,系統不會跳回去執行trycatch裡的任何程式碼。
- public int test(){
- int count = 1;
- try{
- return ++count;
- }finally{
- return ++count;
- }
- }
- //以上程式碼最終返回值是:3
- public int test(){
- int count = 1;
- try{
- return ++count;
- }finally{
- return count++;
- }
- }
- //以上程式碼最終返回值是:2
throw語句的執行和return語句比較類似。當程式執行trycatch塊遇到throw語句時,throw語句會導致該方法立即結束,系統執行throw語句時並不會立即丟擲異常,而是去尋找該異常處理流程中是否包含finally塊。如果沒有finally塊,程式立即丟擲異常。如果有finally塊,系統立即執行finally塊,只有當finally塊執行完成之後,系統才會再次跳出來丟擲異常。如果finally塊裡使用return語句來結束方法,系統將不會跳回去執行try塊,catch塊去丟擲異常。
例如1:
- int count = 1;
- try{
- throw new RuntimeException("異常1");
- }finally{
- return count++;
- }
- //執行結果:返回值是1,同時不會丟擲任何異常。
例如2:
- int count = 1;
- try{
- throw new RuntimeException("異常1");
- }finally{
- throw new RuntimeException("異常2");
- }
- //執行結果:Exception in thread "main" java.lang.RuntimeException: 異常2
1.3 catch塊的用法
1.3.1 catch的順序
對於java的異常捕獲來說,每個try塊至少需要一個catch塊或一個finally塊,絕不能只有單獨一個孤零零try塊。通常情況下,如果try塊被執行一次,則try塊後只有一個catch塊會被執行,絕不可能有多個catch塊被執行。除非在迴圈中使用了continue開始下一次迴圈,下一次迴圈又重新執行了try塊,才可能導致多個catch塊被執行。由於異常處理機制中排在前面的catch(XxxException ex)塊總是會優先獲得執行的機會,因此java對try塊後的多個catch塊的排列順序是有要求的。
因為java的異常有非常嚴格的繼承體系,許多異常類之間有嚴格的父子關係,比如程式FileNotFoundException異常就是IOException的子類。捕獲父類異常的catch塊都應該排在捕捉子類異常的catch塊之後【先處理小異常(子類異常),在處理大異常(父類異常)】,否則將出現編譯錯誤。
例如以下兩個catch語句不能顛倒順序:
- FileInputStream fis = null;
- try{
- fis = new FileInputStream("a.bin");
- fis.read();
- }catch(FileNotFoundException ex){
- ex.printStackTrace();
- }catch(IOException e){
- e.printStackTrace();
- }
1.3.2不要用catch代替流程控制
如下邊這個例子:
- String[] students = {"liyafang","zhoushilong","luorongbo"};
- int i = 0;
- while(true){
- try{
- System.out.println(students[i++]);
- }catch(IndexOutOfBoundsException ex){
- break;
- }
- }
這種遍歷陣列的方式不僅難以閱讀,而且執行速度還非常慢。
千萬不要使用異常來進行流程控制。異常機制不是為流程控制而準備的,異常機制知識為程式的意外情況準備的,因此程式只應該為異常情況使用異常機制。所以,不要使用這種“別出心裁”的方法來遍歷陣列。
1.3.3只能catch可能丟擲的異常
- /*public static void test1(){
- try{
- System.out.println("something");
- }catch(IOException e){
- e.printStackTrace();
- }
- }
- public static void test2(){
- try{
- System.out.println("something");
- }catch(ClassNotFoundException e){
- e.printStackTrace();
- }
- }*/
以上程式碼java編譯器是不允許的。
根據java語言規範,如果一個catch子句試圖捕獲一個型別為XxxException的Checked異常時,那麼它對應的try子句必須可能丟擲XxxException或其子類的異常,否則編譯器將提示該程式具有編譯錯誤—但在所有的Checked異常中,Exception是一個異類,無論try塊是怎樣的程式碼,catch(Exception e)總是正確的。
RuntimeException 類及其子類的例項被稱為Runtime異常,不是RuntimeException類及其子類的異常例項則被稱為Checked異常,只要願意,程式設計師總可以使用catch(XxxException ex)來捕獲執行時異常。
總之,程式使用catch捕捉異常時,其實並不能隨心所欲地捕捉所有異常。程式可以在任意想捕捉的地方捕捉RuntimeException異常,Exception,但對於其他的Checked異常,只有當try塊可能丟擲該異常時(try塊中呼叫的某個方法宣告丟擲了該Checked異常),catch塊才捕捉該Checked異常。
1.3.4實際的修復
如果程式知道如何修復指定異常,應該在catch塊內儘量修復該異常,當該異常情況被修復後可以再次呼叫該方法;如果程式不知道如何修復該異常,也沒有進行任何修復,千萬不要再次呼叫可能導致該異常的方法。
無論如何不要在finally塊中遞迴呼叫可能引起異常的方法,因為這將導致該方法的異常不能被正常丟擲,甚至StackOverflowError錯誤也不能中止程式,只能採用強行結束java.exe程式的方法來中止程式的執行。
1.4 繼承得到的異常
Java語言規定:子類重寫父類的方法時,不能宣告丟擲比父類方法型別更多,範圍更大的異常。也就是說,子類重寫父類方法時,子類方法只能宣告丟擲父類方法所宣告丟擲的異常的子類。
例如:
- public interface Type1 {
- void test() throws ClassNotFoundException;
- }
- public interface Type2 {
- void test() throws NoSuchMethodException;
- }
- class Test implements Type1, Type2 {
- @Override
- public void test() {
- }
- }
上面程式碼的異常處理是正確的。
Test實現了Type1介面,實現Type1介面裡的test()方法時可以宣告丟擲ClassNotFoundException異常或該異常的子類,或者不宣告丟擲;Test類實現了Type2介面,實現了Type2介面裡的test()方法時可以宣告丟擲NoSuchMethodException異常或該異常的子類,或者不宣告丟擲。由於Test類同時實現了Type1,Type2兩個介面,因此需要同時實現兩個介面中的test()方法。只能是上面兩種宣告丟擲的交集,不能宣告丟擲任何異常。
原文轉自:Java異常捕捉陷進
相關文章
- 造成記憶體洩漏的異常處理記憶體
- Bulk 異常引發的 Elasticsearch 記憶體洩漏Elasticsearch記憶體
- 【轉】java中異常與try catch finally詳解Java
- JAVA的異常處理機制(一)——try...catch...finallyJava
- 異常捕捉、抓取
- JAVA: 捕捉啟動時的異常Java
- 異常連線導致的記憶體洩漏排查記憶體
- 異常-try...catch的方式處理異常1
- 異常-try...catch的方式處理異常2
- Java之異常處理try{}catch(){}Java
- JAVA記憶體區域與記憶體溢位異常Java記憶體溢位
- Java編譯異常捕捉與上報筆記Java編譯筆記
- Android常見記憶體洩漏總結Android記憶體
- python異常捕捉處理Python
- JVM(2)-Java記憶體區域與記憶體溢位異常JVMJava記憶體溢位
- Java記憶體洩漏Java記憶體
- C++ exception 異常類繼承關係C++Exception繼承
- Java 筆記《異常》Java筆記
- 用Go語言異常機制模擬TryCatch異常捕捉Go
- .NET WebAPI 利用特性捕捉異常WebAPI
- 異常篇——異常記錄
- java異常體系Java
- python異常處理中finally的作用Python
- 一次django記憶體異常排查Django記憶體
- Java 異常(二) 自定義異常Java
- JS 使用try catch捕獲異常JS
- .NET Core[MVC] 利用特性捕捉異常MVC
- Java異常體系的探究Java
- 【譯】JavaScript的工作原理:記憶體管理和4種常見的記憶體洩漏JavaScript記憶體
- 【java】異常Java
- java 異常Java
- Java 異常Java
- Java異常Java
- 異常JavaJava
- Java異常體系概述Java
- Java記憶體區域與記憶體溢位異常(JVM學習系列1)Java記憶體溢位JVM
- JVM學習-02-Java記憶體區域與記憶體溢位異常JVMJava記憶體溢位
- jmu-Java-06異常-01-常見異常Java
- Java記憶體區域與記憶體溢位異常 - 執行時資料區Java記憶體溢位