1. 程式人生 > >Java 裏的異常(Exception)詳解

Java 裏的異常(Exception)詳解

告訴 ted 所有 lib string vm虛擬機 例子 nts 判斷

作為一位初學者, 本屌也沒有能力對異常談得很深入. 只不過Java裏關於Exception的東西實在是很多. 所以這篇文章很長就是了..

一, 什麽是java裏的異常

由於java是c\c++ 發展而來的, 首先我們先看看c語言裏的錯誤.

1.1 c語言裏的錯誤

我們實現1個程序的過程包括, 代碼編寫, 編譯代碼成為程序, 執行程序.

.

其中大部分常見的語法錯誤都會被編譯代碼這樣部過濾掉. 但是即使通過了編譯. 執行程序這一步可能還是會有錯誤. 原因很多, 例如常見的除數為0, 內存溢出(數組的index超出界限), 或者內存被其他程序修改等. 最簡單的例子: [java]
view plain copy
  1. #include <stdio.h>
  2. int f(int a, int b){
  3. return a/b;
  4. }
  5. int main(){
  6. int i = f(8,0);
  7. printf("i is %d\n",i);
  8. return 0;
  9. }



上面的例子編譯時是無錯的, 但是一旦執行就會提示吐核錯誤了. c語言裏對這種執行時出現的錯誤是無能為力的, 一旦出錯就會整個程序崩潰, 就不會在繼續執行下面的代碼. 而且很多時候出錯信息很少, 讓你無法判斷出錯的原因和地方, 只能一步步小心debug... 所以很多用c寫的程序有時會出現非法關閉的現象. 解決方法只能是在代碼裏對可能出錯的地方添加if 判斷. 例如f()函數裏可以對b進行判斷, 如果是0就不執行.

1.2 java裏運行時出現的錯誤

java裏編譯器對代碼的規範性比c嚴格得多. 但是即使如此, 通過編譯的java程序有時也很難避免執行時出錯. 例如, 將上面的c程序改編成java程序: [java] view plain copy
  1. package Exception_kng;
  2. class Exp1{
  3. public int f(int a, int b){
  4. return a/b;
  5. }
  6. }
  7. public class Expt_1{
  8. public static void g(){
  9. Exp1 e = new Exp1();
  10. int i = e.f(8,0);
  11. System.out.printf("i is %d\n", i);
  12. }
  13. }
運行時一樣會出錯, 下面是出錯信息: [java] view plain copy
  1. [java] Caused by: java.lang.ArithmeticException: / by zero
  2. [java] at Exception_kng.Exp1.f(Expt_1.java:5)
  3. [java] at Exception_kng.Expt_1.g(Expt_1.java:12)
  4. [java] at Enter_1.main(Enter_1.java:31)
但是可以見到, java告訴你出錯的類型: 運算錯誤(ArithmeticExcetion), 出錯信息和出錯的類與文件行數輸出, 方便你調試. jvm虛擬機是會對錯誤作出一定的處理的. 所以可以簡單地將java裏的異常理解成java運行時出現的錯誤, 異常機制就是對這種錯誤進行處理的機制.

1.3 java異常的定義

實際上, 當java程序執行時出現錯誤時, jvm會把執行時出錯的信息(例如出錯原因, 類型, 位置) 收集,然後打包成為1個對象(object), 程序員可以對這種對象進行處理. 這種對象就是所謂的異常. 可能出現的異常的代碼並不是肯定會出現異常, 取決於執行環境和數據.!

二, java裏的異常的分類.

見下圖: Throwable / \ Error Exception / / \ xxxxxx xxxxxx RuntimeException / \ xxxxxx ArithmeticException 上圖的所有對象都是類. Throwable 代表是可拋出的. Error 代表的是嚴重錯誤, 這種錯誤程序員無法進行處理, 例如操作系統崩潰, jvm出錯, 動態鏈接庫失敗等. Error並不是異常, 不是本文的重點. Exception 代表的就是異常了. 它下面很多派生類, 其中它的派生類也分兩種, 一種是RuntimeException(運行時異常), 其他的都是非運行時異常 RuntimeException 包括除數為0, 數組下標超界等. 運行時異常的派生類有很多, 其產生頻率較高. 它的派生類可以由程序處理或者拋給(throw) 給jvm處理. 例如上面的例子就是拋給了jvm處理, jvm把程序中斷執行, 並把錯誤信息輸出到終端上. 非RuntimeExcption 這種異常屬於Excepion的派生類(上面紅色的xxx), 但是不是RuntimeException的派生類, 這種異常必須由程序員手動處理,否則不通過編譯. ArithmeticExcpetion 算術異常, 它是RuntimeException的派生類, 所以程序員不手動處理也通過編譯, 只不過出錯時會被jvm處理.

三, java裏對異常的處理

java裏對異常的處理有三種.

3.1 程序猿對有可能出現的異常使用try catch處理.

例如我們將上面的例子改動一下: [java] view plain copy
  1. package Exception_kng;
  2. class Exp2{
  3. public int f(int a, int b){
  4. int i = 0;
  5. try{
  6. i = a/b;
  7. }
  8. catch(Exception e){
  9. System.out.printf("Exception occurs!!\n");
  10. System.out.println(e.getMessage()); //print the root cause
  11. System.out.printf("===========================\n");
  12. e.printStackTrace(); //print the info of function stuck.
  13. }
  14. return i;
  15. }
  16. }
  17. public class Expt_2{
  18. public static void g(){
  19. Exp2 ex = new Exp2();
  20. int i = ex.f(8,0); //call f()
  21. System.out.printf("i is %d\n", i); //successfully executed
  22. }
  23. }


在f()函數中對可能出現的異常的代碼進行try catch處理後, 程序會執行catch裏的代碼. 而且不會中斷整個程序, 繼續執行try catch後面的代碼. 程序執行輸出: [java] view plain copy
  1. [java] Exception occurs!!
  2. [java] / by zero
  3. [java] ===========================
  4. [java] java.lang.ArithmeticException: / by zero
  5. [java] at Exception_kng.Exp2.f(Expt_2.java:7)
  6. [java] at Exception_kng.Expt_2.g(Expt_2.java:23)
  7. [java] at Enter_1.main(Enter_1.java:31)
  8. [java] i is 0


註意最終也執行了g()函數中的最後一條語句, 輸出了i的值. 也就是說try catch處理後並不會終止程序, 令程序即使出現了錯誤, 也可以對錯誤進行一定的處理後繼續執行. 這就是java異常機制比c語言安全的地方. 下面會詳細講解 try catch. 註: getMessage() 方法: Exception類的方法之一, 返回異常的原因, 上面的 / by zero 就是這個方法輸出的. printStackTrace(): Exception類的方法之一, 在屏幕輸出函數棧信息, 也就是異常出現的地方.

3.2 函數裏並不處理異常, 使用throw or throws 關鍵字 把可能出現的異常拋給調用該函數的上級函數處理.

例如我在f()函數中不想處理可能出現的異常, 想把它拋出上級函數處理: 下面是個例子: [java] view plain copy
  1. package Exception_kng;
  2. class Exp3{
  3. public int f(int a, int b){
  4. if (0 == b){
  5. throw new ArithmeticException("Shit !!! / by zero!");
  6. }
  7. return a/b;
  8. }
  9. }
  10. public class Expt_3{
  11. public static void g() throws ArithmeticException{
  12. Exp3 ex = new Exp3();
  13. int i = 22;
  14. i = ex.f(8,0); //throw excetpion
  15. System.out.printf("i is %d\n", i); //failed executed
  16. System.out.printf("g() is done!!\n"); //failed executed
  17. }
  18. public static void h(){
  19. try{
  20. g();
  21. }catch(ArithmeticException e){
  22. System.out.printf("Exception occurs!!\n");
  23. System.out.println(e.getMessage()); //print the root cause
  24. System.out.printf("===========================\n");
  25. e.printStackTrace(); //print the info of function stuck.
  26. }
  27. System.out.printf("h() is done!!\n"); //successfully executed
  28. }
  29. }
可以見到f() 加了個條件判斷, 如果參數b = 0, 使用throw 直接手動拋出1個異常. 讓調用它的函數處理. g()調用f()函數, 預見到f()可能有異常, 但是也不想處理, 使用throws 關鍵字告訴調用它的函數本函數有可能拋出這種異常. // 註, 這裏的throws對程序並沒有實質的影響. h()調用g(), 簡單g()定義的throws, 用try catch在本函數進行處理. 輸出: [java] view plain copy
  1. [java] Exception occurs!!
  2. [java] Shit !!! / by zero!
  3. [java] ===========================
  4. [java] java.lang.ArithmeticException: Shit !!! / by zero!
  5. [java] at Exception_kng.Exp3.f(Expt_3.java:6)
  6. [java] at Exception_kng.Expt_3.g(Expt_3.java:18)
  7. [java] at Exception_kng.Expt_3.h(Expt_3.java:25)
  8. [java] at Enter_1.main(Enter_1.java:31)
  9. [java] h() is done!!

註意這個程序沒有執行g() 最後的代碼. throw 和 throws 後面也會詳細講解.

3.3 交給jvm虛擬機處理

假如上面的例子h() 也不處理怎麽辦? 就如1.2 的例子, 會拋給jvm處理. 但是這種情況只適用於RuntimeExecption及其派生類. jvm怎麽處理呢, 就是中斷整個程序, 並把異常信息輸出到屏幕上.

實際上, 當java程序的1個函數拋出異常時,
首先會檢查當前函數有沒有try catch處理, 如果無檢查上一級函數有無try..catch處理....
這樣在函數棧裏一級一級向上檢查, 如果直至main函數都無try..catch, 則拋給jvm..
項目中強烈建議盡量手動處理, 不要把異常交給jvm.

四,Try catch finally 的處理機制.

這裏開始詳解try catch finally了. 語法是這樣的. try{ 可能出異常的若幹行代碼; } catch(ExceptionName1 e){ 產生ExceptionName 1的處理代碼; } catch(ExceptionName2 e){ 產生ExceptionName 2的處理代碼; } ... finally{ 無論如何, 最終肯定會執行的代碼
}


4.1 try catch finally的執行路線.

下面用個例子來說明:

[java] view plain copy
  1. try{
  2. f();
  3. ff();
  4. }
  5. catch(ArithmeticException e){
  6. g();
  7. }
  8. catch(IOException e){
  9. gg();
  10. }
  11. catch(AuthorizedException e){
  12. ggg();
  13. }
  14. finally{
  15. h();
  16. }
  17. k();

4.1.1 當try裏面的f()拋出了IOException

當f()拋出了異常, 那麽ff()就不會執行了. 程序會嘗試捕捉異常.

首先捕捉ArithmeticException, 捕捉失敗.

接下來捕捉IOException, 捕捉成功, 執行gg();

一旦捕捉到一個異常, 不會再嘗試捕捉其他異常, 直接執行finally裏的h();

執行後面的函數k().

也就是說路線是:

f() -> gg() -> h() -> k()



有2點要註意的.

1. f()函數極有可能未完整執行, 因為它拋出了異常, 拋出異常的語句執行失敗, 之後的語句放棄執行.

2. try{} 裏面, f()之後的語句, 例如ff()放棄執行.


4.1.2 沒有任何異常拋出

這種情況很簡單, 就是try{}裏面的代碼被完整執行, 因為沒有拋出任何異常, 就不會嘗試執行catch裏的部分, 直接到finally部分了.

路線是:
f() -> ff() -> h() -> k()

4.2 如何確定要捕捉的異常名字.

也許有人會問, 我們怎麽知道到底會拋出什麽異常?

下面有3個解決方案.

1.看代碼憑經驗, 例如看到1段除法的代碼, 則有可能拋出算術異常.

2.在catch的括號裏寫上Exception e, 畢竟Exception 是所有其他異常的超類, 這裏涉及多態的知識, 至於什麽是多態可以看看本人的另一篇文章.

3. 觀察被調用函數的函數定義, 如果有throws後綴, 則可以嘗試捕捉throws 後綴拋出的異常

4.3 為什麽需要finally

包括我在內很多人會覺得finally語句簡直多勾余, 既然是否捕捉到異常都會執行, 上面那個例子裏的h()為什麽不跟下面的k() 寫在一起呢.

上面的例子的確看不出區別.

但下面兩種情況下就體現了finally獨特的重要性.

4.3.1 拋出了1個異常, 但是沒有被任何catch子句捕捉成功.

例如try裏面拋出了1個A異常, 但是只有後面只有捕捉B異常, 和C異常的子句.

這種情況下, 程序直接執行finally{}裏的子句, 然後中斷當前函數, 把異常拋給上一級函數, 所以當前函數finally後面的語句不會被執行.

例子:

[java] view plain copy
  1. package Exception_kng;
  2. import java.net.*;
  3. import java.io.*;
  4. class Exp4{
  5. public int f(int a, int b) throws IOException, BindException{
  6. return a/b;
  7. }
  8. }
  9. public class Expt_4{
  10. public static void g(){
  11. Exp4 ex = new Exp4();
  12. int i = 22;
  13. try{
  14. System.out.printf("g() : try!!\n"); //failed
  15. i = ex.f(8,0); //call f()
  16. }
  17. catch(BindException e){
  18. System.out.printf("g() : BindException!!\n"); //failed
  19. }
  20. catch(IOException e){
  21. System.out.printf("g() : IOException!!\n"); //failed
  22. }
  23. finally{
  24. System.out.printf("g() : finaly!!\n"); //successfully executed
  25. }
  26. System.out.printf("g() is done!!\n"); //failed
  27. }
  28. public static void h(){
  29. try{
  30. g();
  31. }catch(ArithmeticException e){
  32. System.out.printf("Exception occurs!!\n");
  33. System.out.println(e.getMessage()); //print the root cause
  34. System.out.printf("===========================\n");
  35. e.printStackTrace(); //print the info of function stuck.
  36. }
  37. System.out.printf("h() is done!!\n"); //successfully executed
  38. }
  39. }

我所說的情況, 就在上面例子裏的g()函數, g()函數裏嘗試捕捉兩個異常, 但是拋出了第3個異常(ArithmeticException 算術異常).

所以這個異常會中斷g()的執行, 因為沒有被捕捉到, 然後拋給調用g()的 h()函數處理, 而在h()捕捉到了, 所以h()函數是能完整執行的.

也就是說g()裏的

[java] view plain copy
  1. System.out.printf("g() is done!!\n"); //failed

執行失敗

而h()裏的

[java] view plain copy
  1. System.out.printf("h() is done!!\n"); //successfully executed

執行成功

但是無論如何, g()裏的finally{}部分還是被執行了

執行結果如下:

[java] view plain copy
  1. [java] g() : try!!
  2. [java] g() : finaly!!
  3. [java] Exception occurs!!
  4. [java] / by zero
  5. [java] ===========================
  6. [java] java.lang.ArithmeticException: / by zero
  7. [java] at Exception_kng.Exp4.f(Expt_4.java:8)
  8. [java] at Exception_kng.Expt_4.g(Expt_4.java:18)
  9. [java] at Exception_kng.Expt_4.h(Expt_4.java:34)
  10. [java] at Enter_1.main(Enter_1.java:31)
  11. [java] h() is done!!

這種情況是1中編程的低級錯誤, 在項目中是不允許出現.

避免方法也十分簡單, 在catch子句集的最後增加1個catch(Exception e)就ok, 因為Exception是所有異常的超類, 只要有異常拋出, 則肯定會捕捉到.

4.3.2 在catch子句內有return子句.

下面例子:

[java] view plain copy
  1. try{
  2. f();
  3. ff();
  4. }
  5. catch(ArithException e){
  6. g();
  7. return j();
  8. }
  9. catch(IOException e){
  10. gg();
  11. return j();
  12. }
  13. catch(AuthorizedException e){
  14. ggg();
  15. return j();
  16. }
  17. finally{
  18. h();
  19. }
  20. k();

假如在f()函數拋出了IOExcepion 異常被捕捉到.

那麽執行路線就是

f() -> gg() -> j() -> h() -> 上一級function

也就說, 這種情況下finally裏的子句會在return回上一級function前執行. 而後面的k()就被放棄了.

4.3.3 finally作用小結.

可以看出, finally裏的語句, 無論如何都會被執行.

至有兩種情況除外, 一是斷電, 二是exit函數.

在項目中, 我們一般在finally編寫一些釋放資源的動作, 例如初始化公共變量. 關閉connections, 關閉文件等.

4.4 try catch finally裏一些要註意的問題.

4.4.1 無論如何最多只有1個catch被執行

這個上面提到過了, 一旦捕捉到1個異常, 就不會嘗試捕捉其他異常.

如果try裏面的一段代碼可能拋出3種異常A B C,

首先看它先拋出哪個異常, 如果先拋出A, 如果捕捉到A, 那麽就執行catch(A)裏的代碼. 然後finally.. B和C就沒有機會再拋出了.

如果捕捉不到A, 就執行finally{}裏的語句後中斷當前函數, 拋給上一級函數...(應該避免)

4.4.2 有可能所有catch都沒有被執行

兩種情況, 1就是沒有異常拋出, 另一種就是拋出了異常但是沒有捕捉不到(應該避免)

4.4.3 先捕捉子類異常, 再捕捉父類異常, 否則編譯失敗

加入try 裏面嘗試捕捉兩個異常, 1個是A, 1個是B, 但是A是B的父類.

這種情況下, 應該把catch(B)寫在catch(A)前面.

原因也很簡單, 加入把catch(A)寫在前面, 因為多態的存在, 即使拋出了B異常, 也會被catch(A)捕捉, 後面的catch(B)就沒有意義了.

也就是說如果捕捉Exception這個異常基類, 應該放在最後的catch裏, 項目中也強烈建議這麽做, 可以避免上述4.3.1的情況出現.

4.4.4 catch與catch之間不能有任何代碼.

這個沒什麽好說的. 語法規則

4.4.5 finally裏不能訪問catch裏捕捉的異常對象e

每1個異常對象只能由catch它的catch子句裏訪問.

4.4.6 try裏面的定義變量不能在try外面使用.

跟if類似, 不多說了.

4.4.7 try catch finally可以嵌套使用.

這個也不難理解..

五, throw 和throws的機制和用法.

下面開始詳講異常另一種處理方法throw 和 throws了.

註意的是, 這兩種用法都沒有真正的處理異常, 真正處理的異常方法只有try catch, 這兩種方法只是交給上一級方法處理.

就如一個組織裏 , 有1個大佬, 1個黨主, 1個小弟.

大佬叫黨主幹活, 堂主叫小弟幹活, 然後小弟碰上麻煩了, 但是小弟不會處理這個麻煩, 只能中斷工作拋給黨主處理, 然後堂主發現這個麻煩只有大佬能處理, 然後拋給大佬處理..

道理是相通的..

5.1 throw 的語法與作用

throws的語法很簡單.

語法:

throw new XException();

其中xException必須是Exception的派生類.

這裏註意throw 出的是1個異常對象, 所以new不能省略

作用就是手動令程序拋出1個異常對象.

5.2 throw 1個 RuntimeException及其派生類

我們看回上面3.2 的例子:

[java] view plain copy
  1. public int f(int a, int b){
  2. if (0 == b){
  3. throw new ArithmeticException("Shit !!! / by zero!");
  4. }
  5. return a/b;
  6. }

5.2.1 throw會中斷當前函數, 當前函數執行失敗(不完整)

當這個函數的if 判斷了b=0時, 就利用throws手動拋出了1個異常. 這個異常會中斷這個函數. 也就是說f()執行不完整, 是沒有返回值的.

5.2.2, 接下來哪個調用這個函數就會在調用這個函數的語句上收到異常.

[java] view plain copy
  1. public void g(){
  2. int i;
  3. h();
  4. i = f(); //recevie excepton
  5. k();
  6. }

例如上沒的g()函數, 在調用f() 會收到1個異常.

這時g()函數有三種選擇.

1. 不做任何處理

這時, g()收到f()裏拋出的異常就會打斷g()執行, 也就是說g()裏面的k(); 被放棄了, 然後程序會繼續把這個函數拋給調用g()函數.

然後一級一級尋求處理, 如果都不處理, 則拋給jvm處理. jvm會中斷程序, 輸出異常信息. 這個上沒提到過了.

2. 使用try catch處理

如果catch成功, 則g()函數能完整執行, 而且這個異常不會繼續向上拋.

如果catch失敗(盡量避免), 則跟情況1相同.

5.3 throw 1個 非RuntimeException派生類的異常

將上面的例子改一下:

[java] view plain copy
  1. public int f(int a, int b){
  2. if (0 == b){
  3. throw new IOException("Shit !!! / by zero!");
  4. }
  5. return a/b;
  6. }


例如, 我不想拋出ArithmeticException, 我想拋出IOExcetpion.

註意 這裏, IOException雖然邏輯上是錯誤的(完全不是IO的問題嘛), 但是在程序中完全可行, 因為程序猿可以根據需要控制程序指定拋出任何1個異常.

但是這段代碼編譯失敗, 因為IOException 不是 RuntimeException的派生類.

java規定:

5.3.1 如果一個方法裏利用throw手動拋出1個非RuntimeException異常, 必須在函數定義聲明裏加上throws 後綴

改成這樣就正確了:

[java] view plain copy
  1. public int f(int a, int b) throws IOException{
  2. if (0 == b){
  3. throw new IOException("Shit !!! / by zero!");
  4. }
  5. return a/b;
  6. }


註意在方法定義裏加上了throws子句. 告訴調用它的函數我可能拋出這個異常.

5.3.2 調用該方法的方法則必須處理這個異常

例如抄回上面的例子, g()調用f()函數.

[java] view plain copy
  1. public void g(){
  2. int i;
  3. h();
  4. i = f(); //recevie excepton
  5. k()
  6. }


但是編譯失敗.

因為f()利用throws 聲明了會拋出1個非runtimeExcetpion. 這時g()必須做出處理.

處理方法有兩種:

1. try catch自己處理:

[java] view plain copy
  1. public void g(){
  2. int i = 0;
  3. h();
  4. try{
  5. i = f(); //recevie excepton
  6. }
  7. catch(IOException e){
  8. }
  9. k();
  10. }


需要註意的是, catch裏面要麽寫上throws對應的異常(這裏是 IOException), 要麽寫上這個異常的超類, 否則還是編譯失敗.

2.g()利用throws 往上一級方法拋

.

[java] view plain copy
  1. public void g() throws IOException{
  2. int i = 0;
  3. h();
  4. i = f(); //recevie excepton
  5. k();
  6. }

這是調用g()的函數也要考慮上面的這兩種處理方法了...

但是最終上級的方法(main 方法)還是不處理的話, 就編譯失敗, 上面說過了, 非runtimeException無法拋給jvm處理.

雖然這兩種處理方法都能通過編譯, 但是運行效果是完全不同的.

第一種, g()能完整執行.

第二種, g()被中斷, 也就是g()裏面的k(); 執行失敗.

5.4 throws 的語法.

throws稍微比throw難理解點:

語法是:

public void f() throws Exception1, Exception2...{

}

也就是講, thorws可以加上多個異常, 註意這裏拋出的不是對象, 不能加上new.

而且不是告訴別人這個函數有可能拋出這麽多個異常. 而是告訴別人, 有可能拋出這些異常的其中一種.

5.5 throws 的作用.

如果為f()函數加上throws後續, 則告訴調用f()的方法, f()函數有可能拋出這些異常的一種.

如果f()throws 了1個或若幹個非RuntimeException, 則調用f()的函數必須處理這些非RuntimeException, 如上面的g()函數一樣.

如果f() throws的都是RuntimeException, 則調用f()的函數可以不處理, 也能通過編譯, 但是實際上還是強烈建議處理它們.

實際上, 如果1個方法f() throws A,B

那麽它有可能不拋出任何異常.(程序運行狀態良好)

也有能拋出C異常(應該避免, 最好在throws上加上C)

5.6 什麽時候應該用throws

5.6.1 一個函數體裏面手動throw了1個RumtimeException, 則這個函數的定義必須加上throws子句

這個是強制, 告訴別人這個函數內有炸彈.

5.6.2 一個函數內有可能由系統拋出異常.

這個是非強制的, 但是如果你知道一個函數內的代碼有可能拋出異常, 最好還是寫上throws 後綴

無論這個異常是否runtimeExcepion.

5.7 一般情況下,調用1個帶有throws方法時怎麽辦

個人建議, 如果你調用1個函數throws A, B, C

那麽你就在當前函數寫上

try

catch(A)
catch(B)

catch(C)

catch(Exception)

這樣能處理能保證你的函數能完整執行, 不會被收到的異常中斷.

當然如果你允許你的函數可以被中斷, 那麽就可以在當前函數定義加上throws A, B 繼續拋給上一級的函數.

5.8 重寫方法時, throws的範圍不能大於超類的對應方法.

例如你在一個派生類重寫一個方法f(), 在超類裏的f() throws A, B 你重寫方法時就不throws出 A,,B,C 或者throws A和B的超類.

原因也是由於多態的存在.

因為1個超類的引用可以指向1個派生類的對象並調用不同的方法. 如果派生類throws的範圍加大

那麽利用多態寫的代碼的try catch就不再適用.

六, throw和throws一些主要區別.

面試問得多,單獨拉出來寫了:

6.1 throw 寫在函數體內, throws寫在函數定義語句中.

應付面試官.

6.2 throw 是拋出1個異常對象, throws是有能拋出異常的種類

所以throw後面的一般加上new 和exception名字().

而throws後面不能加上new的

6.3 一個方法最多只能throw1個異常, 但是可以throws多個種類異常

因為一旦一個函數throw出1個異常, 這個函數就會被中斷執行, 後面的代碼被放棄, 如果你嘗試在函數內寫兩個throw, 編譯失敗.

而throws 是告訴別人這個函數有可能拋出這幾種異常的一種. 但是最多只會拋出一種.

6.4 如果在一個函數體內throw 1個非runtimeException, 那麽必須在函數定義上加上throws後綴. 但反過來就不是必須的.

原因上面講過了.

七, 自定義異常.

我們可以自定義異常, 只需要編寫1個類, 繼承1個異常類就ok

例子:

[java] view plain copy
  1. package Exception_kng;
  2. class User_Exception1 extends ArithmeticException{
  3. public User_Exception1(String Exception_name){
  4. super(Exception_name);
  5. }
  6. public void printStackTrace(){ //overwrite
  7. super.printStackTrace();
  8. System.out.printf("hey man, i am an user_defined excetpion\n");
  9. }
  10. }
  11. class Exp6{
  12. public int f(int a, int b){
  13. if (0 == b){
  14. throw new User_Exception1("Shit !!! / by zero!"); //use User_defined exception
  15. }
  16. return a/b;
  17. }
  18. }
  19. public class Expt_6{
  20. public static void g() {
  21. Exp6 ex = new Exp6();
  22. int i = 22;
  23. try{
  24. i = ex.f(8,0); //throw excetpion
  25. }catch(User_Exception1 e){
  26. e.printStackTrace();
  27. }
  28. System.out.printf("i is %d\n", i);
  29. System.out.printf("g() is done!!\n");
  30. }
  31. }

上面的類User_Exception1 就是1個自定義異常, 並重寫了printStackTrace()方法.

八,java異常的優缺點.

8.1 c語言是如何處理程序錯誤的.

我們要理解異常的優缺點, 首先看看沒有異常的C語言是如何處理錯誤的.

下面是個例子:

[cpp] view plain copy
  1. //openfile
  2. if (fileOpen() > 0){
  3. //check the length of the file
  4. if (gotLengthOfTheFile() > 0){
  5. //check the memory
  6. if (gotEnoughMemory() > 0){
  7. //load file to memory
  8. if (loadFileToMem() > 0){
  9. readFile();
  10. }else{
  11. errorCode = -5;
  12. }
  13. }else{
  14. errorCode = -5;
  15. }
  16. }else{
  17. errorCode = -5;
  18. }
  19. }else{
  20. errorCode = -5;
  21. }
  22. //handle error
  23. case errorCode....
  24. //release Source
  25. releaseSource();

可以見到c語言處理錯誤有這些特點

1. 大部分精力都在錯誤處理.

2. 需要把各種可能出現的錯誤全部考慮到, 才能保證程序的穩定性.

3. 程序可讀性差, 錯誤處理代碼混雜在其他代碼中.

4. 出錯返回信息少, 一旦出錯難以調試.

5. 一旦出現了未考慮到的錯誤, 資源釋放代碼無法執行.

8.2 java異常機制下是如何編寫上述代碼的.

[java] view plain copy
  1. try{
  2. fileOpen();
  3. gotLengthOfTheFile();
  4. gotEnoughMemory();
  5. loadFileToMem();
  6. readFile();
  7. }
  8. catch(fileOpenFail) { handle1()}
  9. catch(gotLengthOfTheFileFail) { handle2()}
  10. catch(gotEnoughMemoryFail) { handle3()}
  11. catch(loadFileToMemFail) { handle4()}
  12. catch(readFileFail) { handle4()}
  13. catch(Exception e) { handle5()} //catch unexpected error
  14. finally{
  15. releasSource();
  16. }



8.3 java異常機制的優點:

由上面的代碼可以看出部分優點:

1. 業務代碼和錯誤處理代碼分離.

2. 強制程序猿考慮程序的穩定性.

3. 有利於代碼調試(異常信息)

4. 即使任何異常產生, 能保證占用的釋放(finally)

8.4 java異常機制的缺點:

1. 異常嵌套難免影響代碼可讀性

2. 並不能令程序邏輯更加清晰.

3. 異常並不能解決所有問題

Java 裏的異常(Exception)詳解