Java之Exception

原文出處:http://blog.csdn.net/zhangerqing

Exception這個東西,程序中必須會有的,儘管我們很不樂意看到它,可是從另一個角度考慮,有異常則說明程序有問題,有助於我們及時改正。有的時候程序出錯的原因有很多,比如不合法的輸入、類型、空指針甚至內存不足,如果光從軟件來看,我們只知道它出問題了,並不清楚問題出在哪兒,給軟件排錯是個很頭疼的事情,因爲可能出問題的地方太多了,語法上的問題還好點兒,畢竟能從視覺上看出來,有些邏輯上的問題纔是致命的,我們必須從全局出發也許才能找到問題的根源!基於這些,我們需要藉助於異常機制。記得剛學習寫程序的時候,老師總說在有可能出錯的地方,別忘了加入異常處理塊,當時我就想,既然是自己寫的東西,難道還不知道會不會出錯?當時在一段時間內這個問題困擾着我,殊不知程序哪有那麼簡單,後來自己寫得多了才清楚了,異常很重要!本章系Java之美[從菜鳥到高手演變]系列之Exception,通過本章的學習,我們可以基本較爲深入的理解Java中異常處理機制。

 一、簡介

Java爲我們提供了非常完美的異常處理機制,使得我們可以更加專心的去寫程序,有的時候遇到需要添加異常處理塊的地方,像eclipse會自動提示你,感覺很幸福!我們看看異常處理的一些類的結構組成:

從根部開始分爲兩大類:Error和Exception。Error是程序無法處理的錯誤,比如OutOfMemoryError、ThreadDeath等。這些異常發生時,Java虛擬機(JVM)一般會選擇線程終止。Exception是程序本身可以處理的異常,這種異常分兩大類:非運行時異常(發生在編譯階段,又稱checkException)和運行時異常(發生在程序運行過程中,又叫uncheckException)。非運行時異常一般就是指一些沒有遵守Java語言規範的代碼,容易看的出來,並且容易解決的異常,運行時異常是那些在程序運行過程中產生的異常,具有不確定性,如空指針異常等,造成空指針的原因很多,所以運行時異常具有不確定性,往往難以排查,還有就是程序中存在的邏輯錯誤,光從一段代碼中看不出問題,需要縱觀全局才能發現的錯誤,也會造成運行時異常,這就要求我們在寫程序時多多注意,儘量處理去處理異常,當異常發生時,希望程序能朝理想的方面運行!

二、異常的類型

一方面我們可以將異常分爲受控異常和不受控異常,其實一般來講,受控異常就是非運行時異常,不受控異常就是運行時異常和Error。另一方面,我們直接將異常分爲非運行時異常和運行時異常。

三、異常處理的過程

使用try/catch/finally語句塊安裝異常處理程序,每個try塊中包含可能出現異常的語句,每個catch塊中包含處理異常的程序,

[java] view plaincopy
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.         String filename = "d:\\test.txt";  
  5.         try {  
  6.             FileReader reader = new FileReader(filename);  
  7.             Scanner in = new Scanner(reader);  
  8.             String input = in.next();  
  9.             int value = Integer.parseInt(input);  
  10.             System.out.println(value);  
  11.         } catch (FileNotFoundException e) {  
  12.             e.printStackTrace();  
  13.         } finally {  
  14.             System.out.println("this is finally block!");  
  15.         }  
  16.     }  
  17. }  
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.         String filename = "d:\\test.txt";  
  5.         try {  
  6.             FileReader reader = new FileReader(filename);  
  7.             Scanner in = new Scanner(reader);  
  8.             String input = in.next();  
  9.             int value = Integer.parseInt(input);  
  10.             System.out.println(value);  
  11.         } catch (FileNotFoundException e) {  
  12.             e.printStackTrace();  
  13.         } finally {  
  14.             System.out.println("this is finally block!");  
  15.         }  
  16.     }  
  17. }  

如果d盤根目錄下沒有test.txt的話,該程序拋出異常:

this is finally block!
java.io.FileNotFoundException: d:\test.txt (系統找不到指定的文件。)
 at java.io.FileInputStream.open(Native Method)
 at java.io.FileInputStream.<init>(FileInputStream.java:106)
 at java.io.FileInputStream.<init>(FileInputStream.java:66)
 at java.io.FileReader.<init>(FileReader.java:41)
 at Test.main(Test.java:10)

但是finally塊中的語句卻輸出了,這個暫且不談,先記着,在d盤下新建文件test.txt,並輸入內容2232,再來觀察下:

輸出:

2322
this is finally block!

finally塊中的語句依然輸出,說明:不論程序有無異常,finally塊中的語句都會執行。因此finally塊中一般放一些關閉資源的語句。接下來我們繼續做實驗,我們將test.txt中的2322改成abc,看看結果:

this is finally block!
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
 at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
 at java.lang.Integer.parseInt(Integer.java:447)
 at java.lang.Integer.parseInt(Integer.java:497)
 at Test.main(Test.java:13)
該異常中的兩處重點我已經標出來了,一處是紅色的Exception in thread “main”,表明異常拋出的地方,另一處是java.lang.NumberFormatException: For input string: "abc",表明異常的類型,此處我們看看上面之前的那個結果,爲什麼沒有拋出異常出現的地方,仔細觀察源程序,我們發現,程序中我們並沒有顯式聲明NumberFormatException,而FileNotFoundException是我們聲明過的,此處我總結一下就是說:1、如果我在程序中聲明瞭某個異常,則拋出異常的時候,不會顯示出處,直接拋出。2、如果我沒有在程序中聲明,那麼程序會同時拋出異常的出處。這是爲什麼?還有,當我沒有顯式聲明的時候,系統會怎麼辦?這肯定是有一定的規律的,下面我們繼續做實驗:

[java] view plaincopy
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.   
  5.         String filename = "d:\\test.txt";  
  6.   
  7.         // 進行捕捉異常   
  8.         try {  
  9.             FileReader reader = new FileReader(filename);  
  10.             Scanner in = new Scanner(reader);  
  11.             String input = in.next();  
  12.             int value = Integer.parseInt(input);  
  13.             System.out.println(value);  
  14.         } catch (FileNotFoundException e) { // 捕捉FileNotFoundException  
  15.             e.printStackTrace();  
  16.         } catch (NumberFormatException e) { // NumberFormatException  
  17.             e.printStackTrace(); // 打印異常信息 就是形如:at java.lang.NumberFor...的信息  
  18.             System.out.println("I'm here!");  
  19.         } finally {  
  20.             System.out.println("this is finally block!");  
  21.         }  
  22.     }  
  23. }  
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.   
  5.         String filename = "d:\\test.txt";  
  6.   
  7.         // 進行捕捉異常  
  8.         try {  
  9.             FileReader reader = new FileReader(filename);  
  10.             Scanner in = new Scanner(reader);  
  11.             String input = in.next();  
  12.             int value = Integer.parseInt(input);  
  13.             System.out.println(value);  
  14.         } catch (FileNotFoundException e) { // 捕捉FileNotFoundException  
  15.             e.printStackTrace();  
  16.         } catch (NumberFormatException e) { // NumberFormatException  
  17.             e.printStackTrace(); // 打印異常信息 就是形如:at java.lang.NumberFor...的信息  
  18.             System.out.println("I'm here!");  
  19.         } finally {  
  20.             System.out.println("this is finally block!");  
  21.         }  
  22.     }  
  23. }  

我加了一個catch塊,轉麼捕獲NumberFormatException,則程序輸出:

java.lang.NumberFormatException: For input string: "abc"
 at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
 at java.lang.Integer.parseInt(Integer.java:447)
 at java.lang.Integer.parseInt(Integer.java:497)
 at Test.main(Test.java:14)
I'm here!
this is finally block!

沒有輸出異常拋出的地方。繼續改代碼:

[java] view plaincopy
  1. public class Test2 {  
  2.       
  3.     public void open(){  
  4.         String filename = "d:\\test.txt";  
  5.         try {  
  6.             FileReader reader = new FileReader(filename);  
  7.             Scanner in = new Scanner(reader);  
  8.             String input = in.next();  
  9.             int value = Integer.parseInt(input);  
  10.             System.out.println(value);  
  11.         } catch (FileNotFoundException e) {  
  12.             e.printStackTrace();  
  13.             System.out.println("this is test2 block!");  
  14.         }   
  15.     }  
  16. }  
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class Test2 {  
  2.       
  3.     public void open(){  
  4.         String filename = "d:\\test.txt";  
  5.         try {  
  6.             FileReader reader = new FileReader(filename);  
  7.             Scanner in = new Scanner(reader);  
  8.             String input = in.next();  
  9.             int value = Integer.parseInt(input);  
  10.             System.out.println(value);  
  11.         } catch (FileNotFoundException e) {  
  12.             e.printStackTrace();  
  13.             System.out.println("this is test2 block!");  
  14.         }   
  15.     }  
  16. }  
[java] view plaincopy
  1. public class Test3 {  
  2.       
  3.     public void carry() {  
  4.         Test2 t2 = new Test2();  
  5.         try {  
  6.             t2.open();  
  7.         } catch (Exception e) {  
  8.             e.printStackTrace();  
  9.             System.out.println("this is test3 block!");  
  10.         }  
  11.     }  
  12. }  
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class Test3 {  
  2.       
  3.     public void carry() {  
  4.         Test2 t2 = new Test2();  
  5.         try {  
  6.             t2.open();  
  7.         } catch (Exception e) {  
  8.             e.printStackTrace();  
  9.             System.out.println("this is test3 block!");  
  10.         }  
  11.     }  
  12. }  
[java] view plaincopy
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.           
  5.         Test3 t3 = new Test3();  
  6.   
  7.         t3.carry();  
  8.     }  
  9.       
  10. }  
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.           
  5.         Test3 t3 = new Test3();  
  6.   
  7.         t3.carry();  
  8.     }  
  9.       
  10. }  

思路是:Test2類中處理業務,Test3類調用Test2類的open方法,最後在Test類中調用Test3類的carry方法,但是,我將異常拋在Test3中,看看異常輸出的結果:

java.lang.NumberFormatException: For input string: "abc"
 at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
 at java.lang.Integer.parseInt(Integer.java:447)
 at java.lang.Integer.parseInt(Integer.java:497)
 at Test2.open(Test2.java:13)
 at Test3.carry(Test3.java:6)
 at Test.main(Test.java:7)
this is test3 block!

首先,拋出的異常沒有地方信息了,其次輸出了:this is test3 block!,說明該異常是從Test3類中的carry方法拋出的,當我們把Test3類中的異常捕獲語句註釋掉的時候,異常如下:

Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
 at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
 at java.lang.Integer.parseInt(Integer.java:447)
 at java.lang.Integer.parseInt(Integer.java:497)
 at Test2.open(Test2.java:13)
 at Test3.carry(Test3.java:6)
 at Test.main(Test.java:7)

看到此處,我想讀者朋友們應該有一定的感覺了,說了這麼多,就是想說明一點,當程序處理不了異常的時候會怎麼辦?是這樣的:當前方法如果聲明瞭相應的異常處理器,如上面的程序如果加了catch(NumberFormatException e),則直接拋出,但是如果沒有聲明,則會找到它的調用者,如果調用者也沒有做相應的處理,則會一直往前找,直到找到main方法,最後拋出異常,所以上面的現象不難解釋!此處我們簡單總結下異常處理的過程:1、在可能出錯的方法加上try/catch塊語句,來調用異常處理器。2、當異常發生時,直接跳到相應的異常處理器catch中,如果有則拋出異常,執行該catch塊中的語句,如果沒有,則找到它的調用者,直到main方法。3、如果有finally塊,則執行finally塊中的語句。

注意:

1、一個try可對應多個catch。2、有try必須至少有一個catch或者finally(此處經網友actt001指正,多謝!)。3、finally塊不是必須的,可有可無。4、一般情況下,當異常發生時,會執行catch塊中的語句,特殊情況:當main方法中拋出異常時,如果程序聲明瞭該異常處理器,則執行相應的catch塊中的語句,如果程序沒有聲明相應的異常處理器,則不執行catch塊中的語句,直接拋出異常!那麼,這個異常來源於哪兒?既然main中有try/catch語句(雖然不是對應的異常處理器),爲什麼沒有拋出,說明main方法中的try/catch塊根本就沒有捕捉到異常,那麼系統怎麼處理?其實是這樣的,這種情況下,異常被直接丟給JVM,而JVM的處理方式就是:直接中斷你的程序!就是這麼簡單。

四、常見異常

NullPointerException 空指針

空指針異常。當應用試圖在要求使用對象的地方使用了null時,拋出該異常。譬如:調用null對象的實例方法、訪問null對象的屬性、計算null對象的長度、使用throw語句拋出null等等

ClassNotFoundException  找不到類

找不到類異常。當應用試圖根據字符串形式的類名構造類,而在遍歷CLASSPAH之後找不到對應名稱的class文件時,拋出該異常。

ClassCastException   類型轉換

ArithmeticException   算數條件

算術條件異常。譬如:整數除零等。

ArrayIndexOutOfBoundsException  數組越界

數組索引越界異常。當對數組的索引值爲負數或大於等於數組大小時拋出。

 

這塊內容我們會不斷更新,請讀者朋友們在閱讀的同時,不斷提出自己遇到的有意義的異常,不斷充實博文,歡迎讀者積極補充!

 五、異常和錯誤

異常: 在Java中程序的錯誤主要是語法錯誤和語義錯誤,一個程序在編譯和運行時出現的錯誤我們統一稱之爲異常,它是JVM(Java虛擬機)通知你的一種方式,通過這種方式,JVM讓你知道,你已經犯了個錯誤,現在有一個機會來修改它。Java中使用異常類來表示異常,不同的異常類代表了不同的異常。但是在Java中所有的異常都有一個基類,叫做Exception。

錯誤:它指的是一個合理的應用程序不能截獲的嚴重的問題,大多數都是反常的情況,錯誤是JVM的一個故障(雖然它可以是任何系統級的服務)。所以,錯誤是很難處理的,一般的開發人員是無法處理這些錯誤的,比如內存溢出。

六、Assert(斷言)

assert是jdk1.4纔開始支持的新功能,主要在開發和測試時開啓,爲保證性能,在程序正式發佈後通常是關閉的。啓用斷言比較簡單,在啓動參數裏設置-ea或者-enableassertions就可以了.

assert表達式有兩種情況:

1)assert exp1 此時的exp1爲一個boolean類型的表達式

當其值爲true時,運行通過,如果爲false,則會拋出一個相應的AssertionError,注意它可以被catch到。

2)assert exp1 : exp2 此時的exp1同上,而exp2可以爲基本類型或一個Object對象,當exp1的值爲true時,同上,且exp2不會被運算;而當exp1的值爲false時,將會拋出AssertionError,同時將exp2的結果作爲AssertionError構造器中的參數,當使用catch該錯誤時,可利用getMessage()方法打印出exp2的結果。

使用斷言應該注意:斷言只是用來調試程序的工具,不要作爲程序的一部分,或者有人用斷言來代替try/catch,這些都是不對的,1、這和斷言的作用相違背,2、斷言在程序發佈後,是會被關閉的,如果將它作爲程序的一部分,那麼當斷言被關閉後,程序必然會出問題。3、有更好的方法,如try/catch,爲什麼還用斷言。所以,最好不要講斷言作爲程序的一部分,從心裏上你可以把它當做可有可無就行了。

七、常見問題

1、finally和return問題

我們平時說:finally中的內容不論程序有無異常,都會被執行,那麼如果我們的程序在try和catch塊中return了,finally中的還會執行嗎?讀者可以先猜猜看,分析一下,接下來我們做實驗:

[java] view plaincopy
  1. public class FinallyTest {  
  2.   
  3.     public static void main(String[] args) {  
  4.         boolean file = open();  
  5.         System.out.println("this is main return value:" + file);  
  6.     }  
  7.   
  8.     public static boolean open() {  
  9.         String filename = "d:\\test.txtp";  
  10.         try {  
  11.             FileReader reader = new FileReader(filename);  
  12.             Scanner in = new Scanner(reader);  
  13.             String input = in.next();  
  14.             int value = Integer.parseInt(input);  
  15.             System.out.println(value);  
  16.             return true;  
  17.   
  18.         } catch (FileNotFoundException e) {  
  19.             System.out.println("this is catch_for_filenot... block!");  
  20.             return false;  
  21.         } finally {  
  22.             System.out.println("this is finally block!");  
  23.         }  
  24.     }  
  25. }  
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class FinallyTest {  
  2.   
  3.     public static void main(String[] args) {  
  4.         boolean file = open();  
  5.         System.out.println("this is main return value:" + file);  
  6.     }  
  7.   
  8.     public static boolean open() {  
  9.         String filename = "d:\\test.txtp";  
  10.         try {  
  11.             FileReader reader = new FileReader(filename);  
  12.             Scanner in = new Scanner(reader);  
  13.             String input = in.next();  
  14.             int value = Integer.parseInt(input);  
  15.             System.out.println(value);  
  16.             return true;  
  17.   
  18.         } catch (FileNotFoundException e) {  
  19.             System.out.println("this is catch_for_filenot... block!");  
  20.             return false;  
  21.         } finally {  
  22.             System.out.println("this is finally block!");  
  23.         }  
  24.     }  
  25. }  

故意把filename寫錯,造出異常,輸出爲下:

this is catch_for_filenot... block!
this is finally block!
this is main return value:false

從這兒看出來,程序先輸出catch塊中的,後又去執行finally塊中的,雖然在catch中已經返回了,最後執行mian方法中的,而且輸出false,說明catch塊中的也成功返回了。所以,面對疑問,我們可以很肯定的回答,即使有return語句,finally塊也一定會被執行!

2、儘量不要將catch和finally一起使用。

像我上面演示程序那樣,try/catch/finally一起使用,在《Big Java》一書中提到,不建議這樣做,因爲會影響程序的可讀性,最好的做法是:用try/catch嵌套,catch用來捕獲異常,finally用來關閉資源,修改如下:

[java] view plaincopy
  1. public class FinallyTest {  
  2.   
  3.     public static void main(String[] args) {  
  4.           
  5.         boolean file = open();  
  6.         System.out.println("this is main return value:" + file);  
  7.     }  
  8.   
  9.     public static boolean open() {  
  10.           
  11.         String filename = "d:\\test.txtp";  
  12.         try {  
  13.             try {  
  14.                 FileReader reader = new FileReader(filename);  
  15.                 Scanner in = new Scanner(reader);  
  16.                 String input = in.next();  
  17.                 int value = Integer.parseInt(input);  
  18.                 System.out.println(value);  
  19.                 return true;  
  20.   
  21.             } finally {  
  22.                 // 一些關閉資源的操作   
  23.                 System.out.println("this is finally block!");  
  24.             }  
  25.   
  26.         } catch (FileNotFoundException e) {  
  27.             System.out.println("this is catch_for_filenot... block!");  
  28.             return false;  
  29.         }  
  30.     }  
  31. }  
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class FinallyTest {  
  2.   
  3.     public static void main(String[] args) {  
  4.           
  5.         boolean file = open();  
  6.         System.out.println("this is main return value:" + file);  
  7.     }  
  8.   
  9.     public static boolean open() {  
  10.           
  11.         String filename = "d:\\test.txtp";  
  12.         try {  
  13.             try {  
  14.                 FileReader reader = new FileReader(filename);  
  15.                 Scanner in = new Scanner(reader);  
  16.                 String input = in.next();  
  17.                 int value = Integer.parseInt(input);  
  18.                 System.out.println(value);  
  19.                 return true;  
  20.   
  21.             } finally {  
  22.                 // 一些關閉資源的操作  
  23.                 System.out.println("this is finally block!");  
  24.             }  
  25.   
  26.         } catch (FileNotFoundException e) {  
  27.             System.out.println("this is catch_for_filenot... block!");  
  28.             return false;  
  29.         }  
  30.     }  
  31. }  

3、自定義異常

畢竟系統自帶的異常處理器並不能滿足所有需求,因爲對於我們開發人員來說,拋出的異常越細緻,我們越容易找到問題,總不能所有的問題都拋出Exception吧?太籠統了。在實際的開發中,我們可以根據自己的需要,進行自定義異常處理器。

[java] view plaincopy
  1. /** 
  2.  * 自定義異常處理器,繼承Exception或者RuntimeException,依情況而定. 
  3.  * @author erqing 
  4.  * 
  5.  */  
  6. public class NameNotSupportException extends RuntimeException {  
  7.   
  8.     private static final long serialVersionUID = 7295869280641332966L;  
  9.   
  10.     public NameNotSupportException() {  
  11.     }  
  12.   
  13.     public NameNotSupportException(String message) {  
  14.         super(message);  
  15.     }  
  16. }  
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.  * 自定義異常處理器,繼承Exception或者RuntimeException,依情況而定. 
  3.  * @author erqing 
  4.  * 
  5.  */  
  6. public class NameNotSupportException extends RuntimeException {  
  7.   
  8.     private static final long serialVersionUID = 7295869280641332966L;  
  9.   
  10.     public NameNotSupportException() {  
  11.     }  
  12.   
  13.     public NameNotSupportException(String message) {  
  14.         super(message);  
  15.     }  
  16. }  
[java] view plaincopy
  1. public class DefineTest {  
  2.   
  3.     public static void main(String[] args) {  
  4.         String name = "egg";  
  5.         if(!"erqing".equals(name)){  
  6.             throw new NameNotSupportException("erqing");  
  7.         }else{  
  8.             System.out.println("name is OK!");  
  9.         }  
  10.     }  
  11. }  
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class DefineTest {  
  2.   
  3.     public static void main(String[] args) {  
  4.         String name = "egg";  
  5.         if(!"erqing".equals(name)){  
  6.             throw new NameNotSupportException("erqing");  
  7.         }else{  
  8.             System.out.println("name is OK!");  
  9.         }  
  10.     }  
  11. }  
[java] view plaincopy
  1. Exception in thread "main" NameNotSupportException: erqing  
  2.     at DefineTest.main(DefineTest.java:7)  
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. Exception in thread "main" NameNotSupportException: erqing  
  2.     at DefineTest.main(DefineTest.java:7)  

五、自定義異常

      Java確實給我們提供了非常多的異常,但是異常體系是不可能預見所有的希望加以報告的錯誤,所以Java允許我們自定義異常來表現程序中可能會遇到的特定問題,總之就是一句話:我們不必拘泥於Java中已有的異常類型。

      Java自定義異常的使用要經歷如下四個步驟:

      1、定義一個類繼承Throwable或其子類。

      2、添加構造方法(當然也可以不用添加,使用默認構造方法)。

      3、在某個方法類拋出該異常。

      4、捕捉該異常。

複製代碼
/** 自定義異常 繼承Exception類 **/
public class MyException extends Exception{
    public MyException(){
        
    }
    
    public MyException(String message){
        super(message);
    }
}

public class Test {
    public void display(int i) throws MyException{
        if(i == 0){
            throw new MyException("該值不能爲0.......");
        }
        else{
            System.out.println( i / 2);
        }
    }
    
    public static void main(String[] args) {
        Test test = new Test();
        try {
            test.display(0);
            System.out.println("---------------------");
        } catch (MyException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

      運行結果:

1111

六、異常鏈

      在設計模式中有一個叫做責任鏈模式,該模式是將多個對象鏈接成一條鏈,客戶端的請求沿着這條鏈傳遞直到被接收、處理。同樣Java異常機制也提供了這樣一條鏈:異常鏈。

      我們知道每遇到一個異常信息,我們都需要進行try…catch,一個還好,如果出現多個異常呢?分類處理肯定會比較麻煩,那就一個Exception解決所有的異常吧。這樣確實是可以,但是這樣處理勢必會導致後面的維護難度增加。最好的辦法就是將這些異常信息封裝,然後捕獲我們的封裝類即可。

      誠然在應用程序中,我們有時候不僅僅只需要封裝異常,更需要傳遞。怎麼傳遞?throws!!binge,正確!!但是如果僅僅只用throws拋出異常,那麼你的封裝類,怎麼辦??

      我們有兩種方式處理異常,一是throws拋出交給上級處理,二是try…catch做具體處理。但是這個與上面有什麼關聯呢?try…catch的catch塊我們可以不需要做任何處理,僅僅只用throw這個關鍵字將我們封裝異常信息主動拋出來。然後在通過關鍵字throws繼續拋出該方法異常。它的上層也可以做這樣的處理,以此類推就會產生一條由異常構成的異常鏈。

      通過使用異常鏈,我們可以提高代碼的可理解性、系統的可維護性和友好性。

      同理,我們有時候在捕獲一個異常後拋出另一個異常信息,並且希望將原始的異常信息也保持起來,這個時候也需要使用異常鏈。

      在異常鏈的使用中,throw拋出的是一個新的異常信息,這樣勢必會導致原有的異常信息丟失,如何保持?在Throwable及其子類中的構造器中都可以接受一個cause參數,該參數保存了原有的異常信息,通過getCause()就可以獲取該原始異常信息。

      語法:

複製代碼
public void test() throws XxxException{
        try {
            //do something:可能拋出異常信息的代碼塊
        } catch (Exception e) {
            throw new XxxException(e);
        }
    }
複製代碼

      示例:

複製代碼
public class Test {
    public void f() throws MyException{
         try {
            FileReader reader = new FileReader("G:\\myfile\\struts.txt");  
             Scanner in = new Scanner(reader);  
             System.out.println(in.next());
        } catch (FileNotFoundException e) {
            //e 保存異常信息
            throw new MyException("文件沒有找到--01",e);
        }  
    }
    
    public void g() throws MyException{
        try {
            f();
        } catch (MyException e) {
            //e 保存異常信息
            throw new MyException("文件沒有找到--02",e);
        }
    }
    
    public static void main(String[] args) {
        Test t = new Test();
        try {
            t.g();
        } catch (MyException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

      運行結果:

複製代碼
com.test9.MyException: 文件沒有找到--02
    at com.test9.Test.g(Test.java:31)
    at com.test9.Test.main(Test.java:38)
Caused by: com.test9.MyException: 文件沒有找到--01
    at com.test9.Test.f(Test.java:22)
    at com.test9.Test.g(Test.java:28)
    ... 1 more
Caused by: java.io.FileNotFoundException: G:\myfile\struts.txt (系統找不到指定的路徑。)
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:106)
    at java.io.FileInputStream.<init>(FileInputStream.java:66)
    at java.io.FileReader.<init>(FileReader.java:41)
    at com.test9.Test.f(Test.java:17)
    ... 2 more
複製代碼

      如果在程序中,去掉e,也就是:throw new MyException("文件沒有找到--02");

      那麼異常信息就保存不了,運行結果如下:

com.test9.MyException: 文件沒有找到--02
    at com.test9.Test.g(Test.java:31)
    at com.test9.Test.main(Test.java:38)

      PS:其實對於異常鏈鄙人使用的也不是很多,理解的不是很清楚,望各位指正!!!!

七、異常的使用誤區

      首先我們先看如下示例:該實例能夠反映java異常的不正確使用(其實這也是我剛剛學Java時寫的代碼)!!

複製代碼
OutputStreamWriter out = null;
        java.sql.Connection conn = null;
        try {            //   ---------1
            Statement stat = conn.createStatement();
            ResultSet rs = stat.executeQuery("select *from user");
            while (rs.next()){
                out.println("name:" + rs.getString("name") + "sex:"
                        + rs.getString("sex"));
            }
            conn.close();         //------2
            out.close();
        } 
        catch (Exception ex){    //------3
            ex.printStackTrace();    //------4
        }
複製代碼

        1、-----------1

      對於這個try…catch塊,我想他的真正目的是捕獲SQL的異常,但是這個try塊是不是包含了太多的信息了。這是我們爲了偷懶而養成的代碼壞習慣。有些人喜歡將一大塊的代碼全部包含在一個try塊裏面,因爲這樣省事,反正有異常它就會拋出,而不願意花時間來分析這個大代碼塊有那幾塊會產生異常,產生什麼類型的異常,反正就是一簍子全部搞定。這就想我們出去旅遊將所有的東西全部裝進一個箱子裏面,而不是分類來裝,雖不知裝進去容易,找出來難啊!!!所有對於一個異常塊,我們應該仔細分清楚每塊的拋出異常,因爲一個大代碼塊有太多的地方會出現異常了。

     結論一:儘可能的減小try塊!!!

      2、--------2

      在這裏你發現了什麼?異常改變了運行流程!!不錯就是異常改變了程序運行流程。如果該程序發生了異常那麼conn.close(); out.close();是不可能執行得到的,這樣勢必會導致資源不能釋放掉。所以如果程序用到了文件、Socket、JDBC連接之類的資源,即使遇到了異常,我們也要確保能夠正確釋放佔用的資源。這裏finally就有用武之地了:不管是否出現了異常,finally總是有機會運行的,所以finally用於釋放資源是再適合不過了。

      結論二:保證所有資源都被正確釋放。充分運用finally關鍵詞。 

       3、----------3

      對於這個代碼我想大部分人都是這樣處理的,(LZ也是尷尬)。使用這樣代碼的人都有這樣一個心理,一個catch解決所有異常,這樣是可以,但是不推薦!爲什麼!首先我們需要明白catch塊所表示是它預期會出現何種異常,並且需要做何種處理,而使用Exception就表示他要處理所有的異常信息,但是這樣做有什麼意義呢?

      這裏我們再來看看上面的程序實例,很顯然它可能需要拋出兩個異常信息,SQLException和IOException。所以一個catch處理兩個截然不同的Exception明顯的不合適。如果用兩個catch,一個處理SQLException、一個處理IOException就好多了。所以:

      結論三:catch語句應當儘量指定具體的異常類型,而不應該指定涵蓋範圍太廣的Exception類。 不要一個Exception試圖處理所有可能出現的異常。 

      4、----------4

      這個就問題多多了,我敢保證幾乎所有的人都這麼使用過。這裏涉及到了兩個問題,一是,捕獲了異常不做處理,二是異常信息不夠明確。

      4.1、捕獲異常不做處理,就是我們所謂的丟棄異常。我們都知道異常意味着程序出現了不可預期的問題,程序它希望我們能夠做出處理來拯救它,但是你呢?一句ex.printStackTrace()搞定,這是多麼的不負責任對程序的異常情況不理不顧。雖然這樣在調試可能會有一定的幫助,但是調試階段結束後呢?不是一句ex.printStackTrace()就可以搞定所有的事情的!

      那麼怎麼改進呢?有四種選擇:

      1、處理異常。對所發生的的異常進行一番處理,如修正錯誤、提醒。再次申明ex.printStackTrace()算不上已經“處理好了異常”.

      2、重新拋出異常。既然你認爲你沒有能力處理該異常,那麼你就盡情向上拋吧!!!

      3、封裝異常。這是LZ認爲最好的處理方法,對異常信息進行分類,然後進行封裝處理。

      4、不要捕獲異常。

      4.2、異常信息不明確。我想對於這樣的:java.io.FileNotFoundException: ………信息除了我們IT人沒有幾個人看得懂和想看吧!所以在出現異常後,我們最好能夠提供一些文字信息,例如當前正在執行的類、方法和其他狀態信息,包括以一種更適合閱讀的方式整理和組織printStackTrace提供的信息。起碼我公司是需要將異常信息所在的類、方法、何種異常都需要記錄在日誌文件中的。

      所以:

      結論四:既然捕獲了異常,就要對它進行適當的處理。不要捕獲異常之後又把它丟棄,不予理睬。 不要做一個不負責的人。

      結論五:在異常處理模塊中提供適量的錯誤原因信息,組織錯誤信息使其易於理解和閱讀。

      對於異常還有以下幾個注意地方:

     六、不要在finally塊中處理返回值。

    七、不要在構造函數中拋出異常。

 

 

八、try…catch、throw、throws

      在這裏主要是區分throw和throws。

      throws是方法拋出異常。在方法聲明中,如果添加了throws子句,表示該方法即將拋出異常,異常的處理交由它的調用者,至於調用者任何處理則不是它的責任範圍內的了。所以如果一個方法會有異常發生時,但是又不想處理或者沒有能力處理,就使用throws吧!

     而throw是語句拋出異常。它不可以單獨使用,要麼與try…catch配套使用,要麼與throws配套使用。

複製代碼
//使用throws拋出異常
    public void f() throws MyException{
         try {
            FileReader reader = new FileReader("G:\\myfile\\struts.txt");  
             Scanner in = new Scanner(reader);  
             System.out.println(in.next());
        } catch (FileNotFoundException e) {
            throw new MyException("文件沒有找到", e);    //throw
        }  
        
    }
複製代碼

 

 

 

九、總結

      其實對於異常使用的優缺點現在確實存在很多的討論。例如:http://www.cnblogs.com/mailingfeng/archive/2012/11/14/2769974.html。這篇博文對於是否需要使用異常進行了比較深刻的討論。LZ實乃菜鳥一枚,不能理解異常深奧之處。但是有一點LZ可以肯定,那就是異常必定會影響系統的性能。

      異常使用指南(摘自:Think in java)

      應該在下列情況下使用異常。

      1、在恰當的級別處理問題(在知道該如何處理異常的情況下才捕獲異常)。

      2、解決問題並且重新調用產生異常的方法。

      3、進行少許修補,然後繞過異常發生的地方繼續執行。

      4、用別的數據進行計算,以代替方法預計會返回的值。

      5、把當前運行環境下能做的事情儘量做完。然後把相同(不同)的異常重新拋到更高層。

      6、終止程序。

      7、進行簡化。

      8、讓類庫和程序更加安全。(這既是在爲調試做短期投資,也是在爲程序的健壯做長期投資)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章