java異常處理機制詳解


轉自:http://www.cnblogs.com/hy928302776/archive/2013/04/22/3035504.html

異常機制已經成為判斷一門編程語言是否成熟的標准,異常機制可以使程序中異常處理代碼和正常業務代碼分離,保證程序代碼更加優雅,並提高程序健壯性。

         Java異常機制主要依賴於try、catch、finally、throw、throws五個關鍵字。

         1.try:它里面放置可能引發異常的代碼

         2.catch:后面對應異常類型和一個代碼塊,用於表明該catch塊用於處理這種類型的代碼塊,可以有多個catch塊。

         3.finally:主要用於回收在try塊里打開的物力資源(如數據庫連接、網絡連接和磁盤文件),異常機制總是保證finally塊總是被執行。只有finally塊,執行完成之后,才會回來執行try或者catch塊中的return或者throw語句,如果finally中使用了return或者   throw等終止方法的語句,則就不會跳回執行,直接停止。

         4.throw:用於拋出一個實際的異常,可以單獨作為語句使用,拋出一個具體的異常對象。

         5.throws:用在方法簽名中,用於聲明該方法可能拋出的異常。

 

       Java的異常分為兩種,checked異常(編譯時異常)和Runtime異常(運行時異常)

1.       java認為checked異常都是可以再編譯階段被處理的異常,所以它強制程序處理所有的checked異常,而Runtime異常無須處理,java程序必須顯式處理checked異常,如果程序沒有處理,則在編譯時會發生錯誤,無法通過編譯。

2.       checked異常體現了java設計哲學:沒有完善處理的代碼根本不會被執行,體現了java的嚴謹性,

     對於構造大型、健壯、可維護的應用系統而言,錯誤處理是整個應用需要考慮的重要方面。Java異常處理機制,在程序運行出現意外時,系統會生成一個Exception對象,來通知程序,從而實現將“業務功能實現代碼”和“錯誤處理代碼”分離,提供更好的可讀性。

     如果執行try塊里的業務邏輯代碼時出現異常,系統會自動生成一個異常對象,該異常對象被提交給運行環境,這個過程被稱為拋出(throw)異常。Java環境收到異常對象時,會尋找合適的catch塊,如果找不到,java運行環境就會終止,java程序將退出。

     不同的catch塊,視為了針對不同的異常類,提供不同的處理方法。

 

對於錯誤處理機制,主要有如下的兩個缺點:

1.無法窮舉所有異常情況:因為人類的知識是有限的,異常情況總比可以考慮到的情況多,總有漏網之魚

2.錯誤處理代碼和業務實現代碼混雜嚴重影響程序的可讀性,會增加程序維護的難度。

1.使用try...catch捕獲異常

java提出了一種假設,如果程序可以順利完成,那么一切正常,把系統的業務實現代碼放在try塊中定義,所有的異常處理邏輯放在catch塊中進行處理。

即:try{

//業務實現代碼

...

}

catch(Exception e){

輸入不合法

}

上面的格式中try塊和catch塊后的{...}都是不可以省略的!

執行步驟:

1.如果執行try塊中的業務邏輯代碼時出現異常,系統自動生成一個異常對象,該異常對象被提交給java運行環境,這個過程稱為拋出(throw)異常。

2.當java運行環境收到異常對象時,會尋找能處理該異常對象的catch塊,如果找到合適的cathc塊並把該異常對象交給catch塊處理,那這個過程稱為捕獲(catch)異常;如果java運行時環境找不到捕獲異常的catch塊,則運行時環境終止,jav程序也將退出。

注意1:不管程序代碼塊是否處於try塊中,甚至包括catch塊中代碼,只要執行該代碼時出現了異常,系統都會自動生成一個異常對象,如果程序沒有為這段代碼定義任何catch塊,java運行環境肯定找不到處理該異常的catch塊,程序肯定在此退出。

注意2:try塊后可以有多個catch塊,try塊后使用多個catch塊是為了針對不同異常類提供的不同的異常處理方式。當系統發生不同意外情況時,系統會生成不同的異常對象,java運行時就會根據該異常對象所屬的異常類來決定使用哪個catch塊來處理該異常。

注意3:通常情況下,如果try塊被執行一次,則try塊后只有一個catch塊會被執行,絕不可能有多個catch塊被執行,除非在循環中使用類continue開始下一次循環,下一次循環又重新運行了try塊,這才可能導致多個catch塊被執行。

注意4:進行異常捕獲時,一定要記住先捕獲小的異常,再捕獲大的異常。

 

Java的異常類,以及他們的繼承關系:

 java把所有非正常情況分成兩種:異常(Exception)和錯誤(Error),都是繼承自Throwable父類。

 Error錯誤:一般是指虛擬機相關的問題,如系統崩潰,虛擬機出錯誤等,這種錯誤無法恢復或不可能捕獲,將導致應用程序中斷,通常不處理。

 

         Throwable():Throwable 類是 Java 語言中所有錯誤或異常的超類。只有當對象是此類(或其子類之一)的實例時,才能通過 Java 虛擬機或者 Java throw 語句拋出。類似地,只有此類或其子類之一才可以是 catch 子句中的參數類型。

         1.Error(錯誤):一般是指java虛擬機相關的問題,如系統崩潰、虛擬機出錯誤、動態鏈接失敗等,這種錯誤無法恢復或不可能捕獲,將導致應用程序中斷,通常應用程序無法處理這些錯誤,因此應用程序不應該捕獲Error對象,也無須在其throws子句中聲明該方法拋出任何Error或其子類。

         2.Exception:Exception 類及其子類是 Throwable 的一種形式,它指出了合理的應用程序想要捕獲的條件

         (1). SQLException:該異常提供關於數據庫訪問錯誤或其他錯誤的信息。

         (2). RuntimeException 是那些可能在 Java 虛擬機正常運行期間拋出的異常的超類

         (3).IOException:此類為異常的通用類,它是由失敗的或中斷的 I/O 操作生成的。

異常對象包含的常用方法:

1.       getMessage();返回該異常的詳細描述字符

2.       printStackTrace():將該異常的跟蹤棧信息輸出到標准錯誤輸出。

3.       printStackTrace(PrintStream s):將該異常的跟蹤棧信息輸出到指定的輸出流

4.       getStackTrace():返回該異常的跟蹤棧信息。

復制代碼
復制代碼
 1 public class TestException
 2 {
 3 
 4 public static void main(String[] args)
 5 {
 6 
 7     try{
 8         FileInputStream fis=new FileInputStream("a.txt");
 9        }
10     catch(IOException ioe)
11     {
12         System.out.println(ioe.getMessage());
13         ioe.printStackTrace();
14     }
15 
16 }
17 
18 }
復制代碼
復制代碼

 

 

使用finally回收資源

 有時候,程序在try塊里面打開了一些物力資源(比如數據庫連接,網絡連接好磁盤文件等),這些物理資源都必須顯式回收。

因為:java的垃圾回收機制不會回收任何的物理資源,垃圾回收機制只回收堆內存中對象所占用的內存。

 

問題1:那么在哪邊回收這些物理資源呢?

答:在finally塊中,因為如果try塊的某條語句引起一場,該語句后的其他語句通常不會被執行,那將導致位於該語句后的資源回收語句得不到執行;如果在catch塊里進行資源回收,但catch塊完全有可能得不到執行,這也將導致不能及時回收這些物理資源。所以我們不管try塊中的代碼是否出現異常,也不管哪個catch塊會被執行,finally塊總會被執行。

那么:java異常處理的完整語法結構如下:

try
{
     //業務實現邏輯
     ...
}
catch(SubException e)
{
     //異常處理快1
     ...
}
catch(SubException2 e)
{
     //異常處理快2
     ...
}
     ...
finally
{
    //資源回收塊
    ...
}

以上的異常處理語法結構中
注意點1:只有try塊石必須的,也就是說如果沒有try塊,則不可能有后面的catch塊和finally塊;
注意點2:catch塊和finally塊都是可選的,但catch塊和finally塊至少出現其中之一,也可以同時出現;
注意點3:可以有多個catch塊,捕獲父類異常的catch塊必須位於捕獲子類異常的后面;
注意點4:不能只有try塊,既沒有catch塊,也沒有finally塊;
注意點5:多個catch塊必須位於try塊之后,finally塊必須位於所有catch塊之后。

復制代碼
復制代碼
 1 import java.io.FileInputStream;
 2 import java.io.IOException;
 3 
 4 public class TestException
 5 {
 6 
 7     
10     public static void main(String[] args)
11     {
12         // TODO Auto-generated method stub
13         FileInputStream fis = null;
14         try
15         {
16             fis = new FileInputStream("a.txt");
17         } catch (IOException ioe)
18         {
19             System.out.println(ioe.getMessage());
20             // return語句強制方法返回
21             return;
22             // 使用exit來退出虛擬機
23             // System.exit(1);
24         } finally
25         {
26             // 關閉磁盤文件,回收資源
27             if (fis != null)
28             {
29                 try
30                 {
31                     fis.close();
32                 } catch (IOException ioe)
33                 {
34                     ioe.printStackTrace();
35                 }
36             }
37             System.out.println("程序已經執行了finally里德資源回收");
38         }
39     }
40 
41 }
復制代碼
復制代碼

運行程序結果:
a.txt (系統找不到指定的文件。)
程序已經執行了finally里德資源回收

如果將catch塊中的最后兩句注釋放入程序,那么結果為:a.txt (系統找不到指定的文件。)

 以上兩種情況顯示:除非在try塊或者catch塊中調用了退出虛擬機的方法(即System.exit(1);),否則不管在try塊、catch塊中執行怎樣的代碼,出現怎樣的情況,異常處理的finally塊總是會被執行的。不過,一般情況下,不要再finally塊中使用renturn或throw等導致方法終止的語句,因為一旦使用,將會導致try塊、catch塊中的return、throw語句失效。

 

 

復制代碼
復制代碼
 1 public class TestException1
 2 {
 3 
 4     public static boolean test()
 5     {
 6         try
 7         {
 8             return true;
 9         } finally
10         {
11             return false;
12         }
13     }
14 
15     public static void main(String[] args)
16     {
17         boolean a = test();
18         System.out.println(a);
19     }
20 
21 }
復制代碼
復制代碼

 

 

運行結果:false

以上的小程序說明:在finally塊中定義了一個renturn false語句,這將導致try塊中的return true 失去作用!

總結一下這個小問題:

當程序執行try塊,catch塊時遇到return語句或者throw語句,這兩個語句都會導致該方法立即結束,所以系統並不會立即執行這兩個語句,而是去尋找該異常處理流程中的finally塊,如果沒有finally塊,程序立即執行return語句或者throw語句,方法終止。如果有finally塊,系統立即開始執行finally塊,只有當finally塊執行完成后,系統才會再次跳回來執行try塊、catch塊里的return或throw語句,如果finally塊里也使用了return或throw等導致方法終止的語句,則finally塊已經終止了方法,不用再跳回去執行try塊、catch塊里的任何代碼了。

綜上:盡量避免在finally塊里使用return或throw等導致方法終止的語句,否則可能出現一些很奇怪的情況!

異常處理的嵌套

例如catch塊中再次包含了一個完整的異常處理流程,這種在try塊,catch塊或finally塊中包含完整的異常處理流程的情形稱為異常處理的嵌套。異常處理流程的代碼可以放在任何可執行代碼的地方,因此完整的異常處理流程既可放在try塊,也可放在catch塊,也可放在finally塊里。

嵌套的深度沒有很明確的限制,通常沒有必要寫層次太深的嵌套異常處理,會導致程序可讀性降低。

 Checked異常和Runtime異常體系

 java異常被分為兩大類:Checked異常和Runtime異常(運行時異常)。

所有RuntimeException類及其子類的實例被稱為Runtime異常,不是RuntimeException類及其子類的異常實例則被稱為Checked異常。

只有java語言提供了Checked異常,其他語言都沒有提供,java認為Checked異常都是可以被處理(修復)的異常,所以java程序無須顯式的處理Checked異常。如果程序沒有處理Checked異常,該程序在編譯時就會發生錯誤,無法通過編譯。

Checked異常的處理方式:

①:當方法明確知道如何處理異常,程序應該使用try...catch塊來捕獲該異常,然后在對應的catch塊中修補該異常。

②:當方法不知道如何處理異常,應該在定義該方法時聲明拋出該異常。

Runtime異常無須顯式聲明拋出,如果程序需要捕捉Runtime異常,也可以使用try...catch塊來捕獲Runtime異常。

問題是:大部分的方法總是不能明確知道如何處理異常,這就只能聲明拋出異常了。

使用throws拋出異常

使用throws拋出異常的思路是:當前方法不知道如何處理這種類型的異常,該異常應該由上一級調用者處理,如果main方法也不知道應該如何處理這種類型的異常,也可以使用使用throws聲明拋出異常,該異常將交給JVM來處理。

JVM對異常的處理方法:打印異常跟蹤棧的信息,並終止程序運行,所以有很多程序遇到異常后自動結束。

使用throws拋出異常的格式:

throws聲明的拋出的語法格式緊跟在方法之后,可以聲明多個異常類,多個異常類之間以逗號隔開。一旦使用了throws語句聲明拋出異常,就不用再使用try...catch來捕獲異常了。

如:throws ExceptionClass1,ExceptionClass2...

注意點1:如果某段代碼調用了一個帶throws聲明的方法,該方法聲明拋出了Checked異常,這表明該方法希望它的調用者來處理該異常。那么這段代碼要么放在try塊中顯示捕獲該異常,要么這段代碼處於另一個帶throws聲明拋出的方法中。

舉例如下:

復制代碼
復制代碼
 1 //方法一:
 2 
 3 import java.io.FileInputStream;
 4 import java.io.IOException;
 5 
 6 public class TestException2
 7 {
 8 
 9     // test() 方法拋出了異常,那么test()方法的調用者要么放在try塊中顯示捕獲該異常,要么這段代碼處於另一個帶throws聲明拋出的方法中。
10 
11     // 以下為后者的處理方法
12 
13     public static void test() throws IOException
14     {
15         FileInputStream fis = new FileInputStream("a.txt");
16     }
17 
18     public static void main(String[] args) throws Exception
19     {
20         test();
21     }
22 
23 }
復制代碼
復制代碼
復制代碼
復制代碼
 1 //方法二:
 2 
 3 import java.io.FileInputStream;
 4 import java.io.IOException;
 5 
 6 public class TestException2
 7 {
 8 
 9     public static void test() throws IOException
10     {
11         FileInputStream fis = new FileInputStream("a.txt");
12     }
13 
14     public static void main(String[] args)
15     {
16         try
17         {
18             test();
19         } catch (IOException e)
20         {
21             // TODO Auto-generated catch block
22             e.printStackTrace();
23         }
24     }
25 
26 }
復制代碼
復制代碼

使用throws聲明拋出異常時有一個限制:就是方法重寫時的“兩小”中的一條規則:子類方法聲明拋出的異常類型應該是父類方法聲明拋出的異常類型的子類或或相等,子類方法中不允許比父類方法聲明拋出更多異常。即如果子類拋出的異常是父類拋出的異常的父類,那么程序無法通過編譯。

因為Checked異常存在一些不便之處,大部分情況,可以使用Runtime異常,如果程序需要在合適的地方捕獲異常,並對異常進行處理,程序一樣可以用try...catch捕獲Runtime異常。

使用throw拋出異常

當程序出現錯誤時,系統會自動拋出異常,另外,java也允許程序自行拋出異常,自行拋出異常使用throw語句完成!

拋出異常:

如果需要在程序中自行拋出異常,應使用throw語句,throw語句可以單獨使用,throw語句拋出的不是異常類,而是一個異常實例,而且每次只能拋出一個異常實例。throw語句的格式如下:throw ExceptionInstance;

throw語句拋出異常的兩種情況:

1.當throw語句拋出的異常是Checked異常,則該throw語句要么處於try塊里顯式捕獲該異常,要么放在一個帶throws聲明拋出的方法中,即把異常交給方法的調用者處理。

2.當throw語句拋出的異常是Runtime異常,則該語句無須放在try塊內,也無須放在帶throws聲明拋出的方法中,程序既可以顯式使用try...catch來捕獲並處理該異常,也可以完全不理會該異常,把該異常交給方法的調用者處理。

舉例如下:

復制代碼
復制代碼
 1 public class TestException3
 2 {
 3 
 4     public static void throwChecked(int a) throws Exception
 5     {
 6         if (a < 0)
 7         {
 8             
11             throw new Exception("a的值大於0,不符合要求");
12         }
13     }
14 
15     public static void throwRuntime(int a)
16     {
17         if (a < 0)
18         {
19             
22             throw new RuntimeException("a的值大於0,不符合要求");
23         } else
24         {
25             System.out.println("a的值為:" + a);
26         }
27     }
28 
29     public static void main(String[] args)
30     {
31         try
32         {
33             
36             throwChecked(-3);
37         } catch (Exception e)
38         {
39             System.out.println(e.getMessage());
40         }
41         throwRuntime(3);
42     }
43 
44 }
復制代碼
復制代碼

由上面的代碼顯式:自行拋出Runtime異常比自行拋出Checked異常的靈活性更好。

 

 

自定義異常類

用戶自定義異常都應該繼承Exception基類,如果希望自定義Runtime異常,則應該繼承RuntimeException基類。

應以異常類通常需要提供兩種構造器:一個是無參數的構造器,另一個是帶一個字符串的構造器,這個字符串將作為該異常對象的詳細說明(也就是異常對象的getMessage方法的返回值)。

通常情況下,程序會很少自行拋出系統異常,因為異常的類名通常包含了該異常的有用信息,所以在選擇拋出什么異常時,應該選擇合適的異常類,從而可以明確地描述異常情況,這樣程序常常需要定義異常類。

用戶定義異常類,需要基礎Exception基類,如果希望定義RuntimeException基類,就應該繼承該基類,定義異常類時通常需要提供兩種構造器:1,無參的構造器,2,帶字符串的構造器,這個字符串作為該異常對象的詳細說明,(也就是異常對象的getMessage方法返回值),調用super將字符串參數傳給異常對象的message屬性,message屬性就是異常對象的詳細描述信息。

 例子如下:

復制代碼
復制代碼
 1 public class TestException4 extends Exception
 2 {
 3 
 4     public TestException4()
 5     {
 6 
 7     }
 8 
 9     public TestException4(String msg)
10     {
11         super(msg);
12     }
13 
14 }
復制代碼
復制代碼

catch和throw同時使用

前面已有兩種異常處理方法:

1.在異常出現的方法內捕獲並處理,方法的調用者將不能再次捕獲該異常。

2.該方法簽名中聲明拋出該異常,將該異常完全交給方法調用者處理。

但是在實際應用中往往需要更復雜的處理方式,即異常出現的當前方法中,程序只對異常進行部分處理,還有些處理需要在該方法的調用者中才能完成,所以應該再次拋出異常,可以讓該方法的調用者也能捕獲到異常。

為了實現這種靠多個方法協作處理同一個異常的情形,可以通過catch塊中結合throw來完成。

舉例catch和throw同時使用的例子:

復制代碼
復制代碼
 1 public class TestException4
 2 {
 3     // 以下AuctionException這個異常是自定義的異常類
 4     private double initPrice = 30.0;
 5 
 6     public void bid(String bidPrice) throws AuctionException
 7     {
 8         double d = 0.0;
 9         try
10         {
11             d = Double.parseDouble(bidPrice);
12         } catch (Exception e)
13         {
14             e.printStackTrace();
15             throw new AuctionException("競拍價必須是數值,不能包含其他字符!");
16         }
17         if (initPrice > d)
18         {
19             throw new AuctionException("競拍價比起拍價低,不允許競拍!");
20         }
21         initPrice = d;
22     }
23 
24     public static void main(String[] args)
25     {
26         TestException4 ta = new TestException4();
27         try
28         {
29             ta.bid("df");
30         } catch (AuctionException ae)
31         {
32             // TODO: handle exception
33             System.err.println(ae.getMessage());
34         }
35     }
36 }
復制代碼
復制代碼

catch和throw同時使用來處理異常的方法是在大型企業中比較常用的。

java的異常跟蹤棧

異常對象的printStackTrace方法用於打印異常的跟蹤棧信息,根據printStackTrace方法的輸出結果,我們可以找到異常的源頭,並跟蹤到異常一路觸發的過程。

雖然printStackTrace()方法可以很方便地追蹤異常的發生狀況,可以用它來調試,但是在最后發布的程序中,應該避免使用它。而應該對捕獲的異常進行適當的處理,而不是簡單的將信息打印出來。

總之,要合理使用異常。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM