java中的異常處理機制

java中的異常處理機制

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

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

         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異常(運行時異常) spa

1.       java認爲checked異常都是能夠再編譯階段被處理的異常,因此它強制程序處理全部的checked異常,而Runtime異常無須處理,java程序必須顯式處理checked異常,若是程序沒有處理,則在編譯時會發生錯誤,沒法經過編譯。 .net

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():返回該異常的跟蹤棧信息。

複製代碼
 
public class TestException
 {
 
 public static void main(String[] args)
 {
 
     try{
         FileInputStream fis=new FileInputStream("a.txt");
        }
     catch(IOException ioe)
     {
         System.out.println(ioe.getMessage());
         ioe.printStackTrace();
     }
 
 }
 
 }

 

 

使用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塊以後。


複製代碼


import java.io.FileInputStream;
 import java.io.IOException;
 
 public class TestException
 {
 
     /**
      * @param args
      */
     public static void main(String[] args)
     {
         // TODO Auto-generated method stub
         FileInputStream fis = null;
         try
         {
             fis = new FileInputStream("a.txt");
         } catch (IOException ioe)
         {
             System.out.println(ioe.getMessage());
             // return語句強制方法返回
             return;
             // 使用exit來退出虛擬機
             // System.exit(1);
         } finally
         {
             // 關閉磁盤文件,回收資源
             if (fis != null)
             {
                 try
                 {
                     fis.close();
                 } catch (IOException ioe)
                 {
                     ioe.printStackTrace();
                 }
             }
             System.out.println("程序已經執行了finally裏德資源回收");
         }
     }
 
 }


複製代碼


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

若是將catch塊中的最後兩句註釋放入程序,那麼結果爲:a.txt (系統找不到指定的文件。)

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

 

 


複製代碼


public class TestException1
 {
 
     public static boolean test()
     {
         try
         {
             return true;
         } finally
         {
             return false;
         }
     }
 
     public static void main(String[] args)
     {
         boolean a = test();
         System.out.println(a);
     }
 
 }


複製代碼


 

 

運行結果: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聲明拋出的方法中。

舉例以下:

複製代碼

//方法一:
 
 import java.io.FileInputStream;
 import java.io.IOException;
 
 public class TestException2
 {
 
     // test() 方法拋出了異常,那麼test()方法的調用者要麼放在try塊中顯示捕獲該異常,要麼這段代碼處於另外一個帶throws聲明拋出的方法中。
 
     // 如下爲後者的處理方法
 
     public static void test() throws IOException
     {
         FileInputStream fis = new FileInputStream("a.txt");
     }
 
     public static void main(String[] args) throws Exception
     {
         test();
     }
 
 }

複製代碼


//方法二:
 
 import java.io.FileInputStream;
 import java.io.IOException;
 
 public class TestException2
 {
 
     public static void test() throws IOException
     {
         FileInputStream fis = new FileInputStream("a.txt");
     }
 
     public static void main(String[] args)
     {
         try
         {
             test();
         } catch (IOException e)
         {
             // TODO Auto-generated catch block
             e.printStackTrace();
         }
     }
 
 }


使用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來捕獲並處理該異常,也能夠徹底不理會該異常,把該異常交給方法的調用者處理。

舉例以下:

public class TestException3
 {
 
     public static void throwChecked(int a) throws Exception
     {
         if (a < 0)
         {
             /**
              * 自行拋出Exception異常 改代碼必須處於try塊裏,或處於帶throws聲明的方法中
              */
             throw new Exception("a的值大於0,不符合要求");
         }
     }
 
     public static void throwRuntime(int a)
     {
         if (a < 0)
         {
             /**
              * 自行拋出RuntimeException異常,既能夠顯式捕獲該異常 也能夠徹底不用理會該異常,把該異常交給方法的調用者處理
              */
             throw new RuntimeException("a的值大於0,不符合要求");
         } else
         {
             System.out.println("a的值爲:" + a);
         }
     }
 
     public static void main(String[] args)
     {
         try
         {
             /**
              * 此處調用了帶throws聲明的方法,必須顯示捕獲該異常(使用try...catch) 不然,要在main方法中再次聲明拋出
              */
             throwChecked(-3);
         } catch (Exception e)
         {
             System.out.println(e.getMessage());
         }
         throwRuntime(3);
     }
 
 }

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

java的異常跟蹤棧

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

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

總之,要合理使用異常。

相關文章
相關標籤/搜索