Java異常機制

一、異常處理概念

  1. 引文
    異常是指在程序運行之中因爲代碼而產生的一種錯誤,在不支持異常處理的程序語言之中,每一個運行時所發生的的錯誤必須由程序員手動控制。但是Java語言之中的異常處理機制則會避免這些問題。

  2. 錯誤與異常
    軟件開發之中,程序的錯誤是無法避免的。程序中的錯誤會有很多種類,一般分爲三種,語法錯誤,語義錯誤和邏輯錯誤三種。
    即便程序中有三種心智的錯誤,但是Java系統中根據錯誤的嚴重程度不同,將程序運行時出現的錯誤分爲兩類,錯誤以及異常。
    錯誤指的是程序在執行過程中所遇到的硬件或操作系統的錯誤,如內存溢出,虛擬機錯誤等,錯誤會導致程序無法運行。
    異常則是指硬件和操作系統正常時,程序遇到的運行錯誤,有些異常是由於算法的錯誤引起,或者疏忽引起,如除數爲0,數組下標越界等等。 Java語言的異常處理機制使程序本身可以捕獲和處理異常。
    因爲異常時可以監測和處理的,所以就會產生相應的異常處理機制,目前絕大多數的語言都提供了異常處理機制,而錯誤處理一般由系統承擔,語言的本身不提供錯誤處理機制。

  3. Java語言的異常處理機制
    Java語言提供的異常處理機制是通過面向對象的方法來處理異常的,所以在Java語言中,所有異常都是以類的形式存在的,除了內置的異常類以外,Java語言也允許用戶自行定義異常類。
    在一個程序的運行過程之中,如果發生了異常事件,則代表該異常產生了一個“異常對象”,並把它交給運行系統,再由運行系統尋找相應的代碼處理這一異常。生成異常並把它交給運行系統的過程稱爲拋出異常。異常本身作爲一個對象,產生一個異常就是產生一個異常對象。該異常對象中可能包含了異常時間類型以及發生異常時應用程序目前的狀態和調用過程等必要的信息。
    異常跑出後,運行系統從生成異常對象的代碼開始,沿方法的調用棧逐層回溯查找,查找到包含相應異常處理的方法,並把異常對象提交給該方法爲止,這一過程稱爲捕獲異常。
    Java語言之中定義了許多異常類,每個異常類都代表一種運行錯誤,類中包含了該運行錯誤的信息和處理錯誤的方法等內容,每當Java程序運行過程中發生一個可識別的運行錯誤時,即該錯誤有一個異常類與之對應,系統均會產生一個相應的該異常類的對象,一旦一個異常對象產生了,系統之中就一定會有相應的機制處理它,從而會保證整個程序運行時候的安全性。
    異常的本質是一個在程序執行過程中發生的事件,這個事件將會中斷程序的正常執行,當Java方法的內部發生異常的時候,這個方法即會創建一個該異常的對象,並且把它傳遞給運行時環節。創建一個異常對象並將它傳遞給運行時的環境的過程就是拋出一個異常。運行時候環境從異常發生的方法開始查找異常處理程序,如果異常處理程序捕獲到的異常類型和這個程序能夠處理異常的類型相同,那麼這個程序就叫做合適的異常處理程序,然後異常處理機制將控制權從發生異常的程序交給能處理該異常的異常處理程序,如果沒有找到合適的異常處理程序,運行時環境將終止程序執行。
    簡單來說,就是發現異常的代碼可以“拋出”一個異常,運行系統能夠“捕獲”該異常,並且交由程序員編寫的相應代碼進行異常處理。

二、異常處理類

  • 因爲Java語言中定義了許多異常類,且每個異常類代表着一種運行錯誤,所以說,Java原因的異常類是處理運行時錯誤的特殊類,類中包含了該運行錯誤的信息和處理錯誤的方法等內容。
  • 在異常類的層次最上層有一個單獨的類叫做Throwable,是java.lang包中的一個類,這個類用來表示所有的異常情況,該類派生了兩個子類java.lang.Errorjava.lang.ExceptionError代表着程序運行時Java系統內部的錯誤。包括,內存溢出錯,棧溢出錯,動態鏈接錯等等。通常Java程序不對這種錯誤進行直接的處理,而是交由操作系統進行處理。Exception則是供應用程序使用的,是用戶程序能夠捕捉到的異常情況,一般情況之下,通過產生它的子類來創建自己的異常,即Exception類對象是Java程序拋出和處理的對象,它有各種不同的子類分別對應於各種不同類型的異常。因爲應用程序不處理Error類,所以一般來說的異常都是指Exception類及其子類。
    在這裏插入圖片描述
  • Java程序對錯誤與異常的處理方式有三種:1.程序不能處理的錯誤。2.程序應該避免而可以不去捕獲的運行時異常。3.必須捕獲的非運行時的異常。

三、異常的處理

  • Java中,異常處理是通過try,catch,finally,throw,throws五個關鍵字來實現的。
  • 不要看異常的理論一大堆,用起來海星的。
  • 例如:
public class Employee {

    private String empNo;
    private String name;
    private int salary;

    public String getEmpNo(){
        return this.empNo;
    }
    public String getName(){
        return this.name;
    }
    public int getSalary(){
        return this.salary;
    }

    public void setEmpNo(String empNo){
        this.empNo = empNo;
    }
    public void setName(String name){
        this.name = name;
    }
    public void setSalary(int salary){
        this.salary = salary;
    }

    public static Employee str2Employee(String str) throws Exception{
        String[] out = str.split(",");
        if(out.length!=3){//如果輸入的格式不正確,數組分割就不是三個了
            throw new Exception("請按照規定的格式輸入信息");//拋出一個異常
        }
        Employee ex = new Employee();
        ex.setEmpNo(out[0]);
        ex.setName(out[1]);
        ex.setSalary(Integer.valueOf(out[2]));
        return ex;
    }

    public static void main(String[] args){
        String info="00001,Lily,2000";
        try{
            Employee emp= Employee.str2Employee(info);
            System.out.println(emp.getEmpNo()+"-"+emp.getName()+"-"+emp.getSalary());
        }catch(Exception e){//捕獲異常
            System.out.println("輸出格式錯誤,請重輸");
        }

    }
  1. try:表示程序正常執行代碼,但是如果程序在執行try代碼塊的時候出現了"非預期"的情況,JVM則會產生一個異常對象,而這個異常對象則會被後面相應的catch塊捕獲。
  2. catch:表示一個異常捕獲塊,當程序執行try塊引發異常的時候,這個異常對象則會被catch給捕獲。
  3. throw:用於手動拋出異常對象,throw後面需要一個異常對象。
  4. throws:用於方法簽名中聲明拋出一個或者多個異常類,throws關鍵字後面可以緊跟一個或者多個異常類。
  5. finally:代表異常處理流程中總會執行的代碼塊。

對於一個完整的異常處理流程而言,try塊是必須的,後面可以跟一個或者多個catch塊,最後還可以帶一個finally塊。

try{
	//要檢查的語句塊
}catch(異常類名 形參對象名){
	//異常發生時的處理語句序列
}finally{
	//一定會運行的語句序列
}

四、拋出異常

捕獲一個異常前,必須要有一段代碼生成一個異常對象並把它拋出,根據異常類型的不同,拋出異常的方法也不相同。可以分爲:1.系統自動拋出的異常。2.指定方法拋出異常。

所有系統定義的運行時異常都可以有系統自動拋出,而指定方法拋出異常需要是用關鍵字throwthrows來明確制定在方法內拋出異常。如用戶程序自定義的異常不可能依靠系統自動拋出,這種情況就必須藉助於throwthrows語句來定義何種情況算是產生了此種異常對應的錯誤,並應該拋出了這個異常。

  1. 拋出異常的方法與調用方法處理異常
    實際編程中,異常的處理可能會在方法之外進行處理,就如我們上面寫的例子。那麼,該方法就拋出異常,有方法的調用者負責處理該異常。這個時候與異常有關的方法就只有兩個:一個是拋出異常的方法,一個是處理異常的方法。

    1. 拋出異常的方法
      如果在一個方法內部的語句執行時引發某種異常,但是不能夠確定如何進行處理,則此方法應該聲明拋出異常,表示該方法不對這些異常進行處理,而由該方法的調用者去進行處理。則在方法之內的異常我們並未使用try-catch語句捕獲異常和處理異常的代碼。一個方法聲明拋出異常有如下的兩種方式,
      方式一:在方法體內使用throw語句拋出異常對象。
      throw 由異常類所產生的對象;
      其中,有“異常類所產生的的對象”是一個從Throwable派生的異常類對象。
      方式二:在方法頭部添加throws子句表示方法將拋出異常。帶有throws子句的方法聲明格式如下:
      [修飾符] 返回值類型 方法名(參數列表) throws 異常類列表

    2. 處理異常的方法
      由一個方法拋出異常之後,該方法內沒有處理異常的語句,則系統就回將異常想上傳遞,由調用他的方法來處理這些異常,若是上層調用方法中仍然沒有處理異常的語句,則可以再往上追溯到更上層,這樣子可以一層一層網上追溯,一直到main()方法,這時JVM肯定會處理的,這樣子可以編譯通過了。也就是說,如果某個方法拋出了異常之後,調用它的方法肯定要捕獲並處理異常,否則就會出現錯誤的。

      public class demo {
          public static void main(String[] args) {
              int num1 = 5, num2 = 0;
              try{
                  if (num2 == 0)
                      throw new ArithmeticException();
                  else
                      System.out.println(num1 + "/" + num2 + "=" + num1 / num2);
              }catch(ArithmeticException e){
                  System.out.println(e);
                  e.printStackTrace();//輸出當前異常對象的對戰使用軌跡
              }
          }
      }
      /*輸出
      java.lang.ArithmeticException
      java.lang.ArithmeticException
      	at sample.demo.main(demo.java:8)
      */
      

      在這個例子中,我們故意從try塊中拋出由異常類所產生的的對象,但是即便不使用throw拋出異常,系統也會自動拋出異常的。

      public class demo {
          public static void main(String[] args) {
              int num1 = 5, num2 = 0;
              try{
                      System.out.println(num1 + "/" + num2 + "=" + num1 / num2);
              }catch(Exception e){
                  System.out.println(e);
                  e.printStackTrace();
              }
          }
      }
      /*
      java.lang.ArithmeticException: / by zero
      java.lang.ArithmeticException: / by zero
      	at sample.demo.main(demo.java:7)
      */
      

      所以說在程序代碼中拋出系統定義的運行時異常並沒有太大的意義,通常從程序代碼中拋出的是自己編寫的異常,因爲系統並不會自動幫我們拋出他們。

    3. 由方法拋出異常交系統處理
      對於程序需要處理的異常,一般編寫try-catch-finally語句捕獲並處理,但是對於程序中無法處理必須交由系統處理的異常,由於系統直接調用的是主方法main(),所以可以在主方法頭使用throws子句聲明拋出異常交由系統處理。如下面程序,編譯可以通過,運行也可以。

      import java.io.FileInputStream;
      import java.io.IOException;
      
      public class demo {
          public static void main(String[] args) throws IOException {
              FileInputStream fis = new FileInputStream("autoexec.bat");
          }
      }
      

      直接在主方法main中拋出異常,讓Java默認的異常處理機制來進行處理,即若在主方法中沒有使用try-catch語句捕獲異常,則必須在聲明主方法頭部後加上throws IOException子句。

       import java.io.BufferedReader;
       import java.io.IOException;
       import java.io.InputStreamReader;
       
       public class demo{
           public static void main(String[] args) {
               String str;
               BufferedReader buf;
               buf = new BufferedReader(new InputStreamReader(System.in));
               while(true){
                   try{
                       System.out.println("請輸入字符串:");
                       str = buf.readLine();
                       if(str.length() > 0)
                           break;
                       else
                           throw new IOException();
                   }catch (IOException e){
                       System.out.println("必須輸入字符串!");
                       continue;
                   }
               }
               String s = str.toUpperCase();
               System.out.println("轉換大寫後字符串爲:"+s);
           }
       }
      

五、自動關閉資源的try語句

Java程序中經常要創建一些對象(如打開的文件,數據連接等),這些對象在使用之後需要關閉,如果忘記關閉可能會引起一些問題,在JDK7之前,通常使用finally子句來確保一定會調用close()方法關閉打開的資源,但是如果調用close()方法也可能拋出異常,這個時候就需要在finally塊內嵌套一個try-catch語句,這樣會顯得程序代碼就回冗長。但是自從JDK7之後,爲開發人員提供了自動關閉資源的功能,這樣也爲管理資源提供了一種更加簡便的方式,這種功能是通過try-with-resources語句,也稱爲自動資源管理語句。該語句能夠自動關閉在try-catch語句塊中使用的資源,該處資源指的是程序完成之後,必須關閉的對象,try-with-resources語句確保了每個資源在語句結束時被關閉。try-with-resources語句的格式如下:

try(聲明或初始化資源的代碼){
	使用資源對象res的語句
}

其中聲明或初始化資源的時候,初始化一個或多個資源的時候,需要使用“;”進行分隔開,當try語句執行結束時會自動關閉這些資源,需強調一點,並非所有資源都可以自動關閉的,只有實現java.lang.AutoCloseable接口的那些資源可以自動關閉,而改接口之中只有一個抽象方法:void close() throws Exception
自動關閉的try語句相當於包含了隱式的finally語句塊,該finally語句塊會自動調用res.close()方法關閉前面所訪問的資源。因此自動關閉資源的try語句後面可以帶有一個或多個finally塊,也可以沒有。如果含有catchfinally子句,則catchfinally子句將會在try-with-resources語句中打開的資源被關閉之後的到調用。

public class demo {
    public static void main(String[] args) throws IOException {
        try(Scanner in = new Scanner(Paths.get("t.txt"))){
            while (in.hasNext())
                System.out.println(in.nextLine());
        }
    }
}

輸出t.txt的內容,Scanner類是實現了AutoCloseable接口的類,所以實現了close()方法,所以對象in是可以自動關閉的資源。當代碼塊退出或者發生異常的時候都會自動調用in.close()方法關閉資源,與finally子句用法相同。如果資源關閉時候出現異常,那麼try語句塊中其他異常都會被忽略,可以在catch語句塊中調用getSuppressed()方法將“被忽略的異常”重新顯示出來。

六、自定義異常類

創建自定義異常類:

  • 聲明一個新的異常類,用戶定義的異常類必須是Throwable類的直接或間接子類。Java推薦用戶自定義的異常類一Exception爲直接弗雷,也可以使用某個已經存在的系統異常類或用戶自己定義的異常類爲其父類。
  • 爲用戶自定義的異常類定義屬性和方法,或覆蓋父類的屬性和方法,使得這些屬性和方法能夠體現該類所對應的錯誤信息。
  • 但是用戶自定義異常不能夠自動拋出,需要藉助於throw語句來定義何種情況算是產生了該異常所對應的錯誤,並且拋出這個異常類的對象。
package sample;

class circleException extends Exception{
    double radius;
    circleException(double r){
        this.radius = r;
    }
    public String toString(){
        return "半徑 r=" + this.radius + "不是一個正數";
    }
}

class circle{
    private  double radius;
    public void setRadius(double r) throws circleException{
        if(r < 0){
            throw new circleException(r);
        }
        else radius = r;
    }
    public void show(){
        System.out.println("圓的面積是:" + 3.14*this.radius*this.radius);
    }
}

public class demo{
    public static void main(String[] args) {
        circle cir = new circle();
        try {
            cir.setRadius(-2.0);
        }catch (circleException e){
            System.out.println(e.toString());
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章