JavaSE基礎:異常處理

胖先森發表於2018-02-12

異常處理

1.為什麼要處理異常?

在學習過程或者未來工作當中,我們重來不希望寫的程式碼有錯誤,不會出現問題,使用者操作永遠邏輯清晰而正確,一切都按照我們祈禱的那樣執行,然而這是不可能的。必然會有錯誤必然會要我們去處理,但是錯誤的處理並不是我們程式碼的核心。

Java 異常機制可以使程式中異常處理程式碼和正常業務程式碼分離,保證程式程式碼更加優雅,並提高程式健壯性。(這段話需要我們經歷了才能去理解,慢慢來)

在有效使用異常的情況下,異常能清晰的回答 what, where, why 這 3 個問題:異常型別回答了“什麼”被丟擲,異常堆疊跟蹤回答了“在哪“丟擲,異常資訊回答了“為什麼“會丟擲。(熟練使用之後就好了)

Java的異常機制依靠於try,catch,finally,throw,throws關鍵字,其中try塊中通常放置可能引發異常的程式碼,catch後對應異常型別和響應的異常處理程式碼塊,finally塊在java異常機制中總是會被執行,通常用於回收try塊中開啟的物理資源,throw用於丟擲一個實際的異常(異常的例項),throws主要在方法簽名中使用,用於宣告該方法可能會丟擲的異常,方便或者提醒方法的使用者來捕獲並處理異常。

2.概念

Java 異常是 Java 提供的用於處理程式中錯誤的一種機制。

所謂錯誤是指在程式執行的過程中發生的一些異常事件(如:除 0 溢位,陣列下標越界,所要讀取的檔案不存在等)。

設計良好的程式應該在異常發生時提供處理這些錯誤的方法,使得程式不會應為異常的發生而阻斷或產生不可預見的結果。

  • Java 程式的執行過程中如出現異常事件,可以生成一個異常類物件,該異常物件封裝了異常事件的資訊並提交給 Java 執行時系統,這個過程稱為丟擲(throw)異常。
  • 當 Java 執行時系統接受到異常物件時,會尋找能處理這一異常的程式碼並把當前異常物件交給其處理,這一過程稱為捕獲(catch)異常。

3.分類

請一定要記住這張圖

###(1) Throwable(老祖宗)

有兩個重要的子類:Exception 和 Error,二者都是 Java 異常處理的重要子類,各自都包含大量子類。

(2) Error(無能為力)

是程式無法處理的錯誤,通常發生於虛擬機器自身,表示執行應用程式中較嚴重問題。例如,Java虛擬機器執行錯誤(Virtual MachineError),當 JVM 不再有繼續執行操作所需的記憶體資源時,將出現 OutOfMemoryError。這些異常發生時,Java 虛擬機器(JVM)一般會選擇執行緒終止。

不需要我們進行捕獲處理

(3) Exception(明辨是非)

  • Checked異常(檢查異常或者編譯異常)

    必須處理的異常:Checked異常是Java特有的,在java設計哲學中,Checked異常被認為是可以被處理或者修復的異常,所以Java程式必須顯式處理Checked異常,當我們使用或者出現Checked異常類的時候,程式中要麼顯式try- catch捕獲該異常並修復,要麼顯式宣告丟擲該異常,否則程式無法通過編譯。(Checked異常某種程度上降低了開發生產率和程式碼執行率,在java領域是一個備受爭論的問題,我個人堅持使用異常處理業務邏輯,那麼點效率可以忽略)

    演示程式碼:會有錯誤報出來,我們需要對程式程式碼進行處理

    package com.shxt.demo01;
    
    import java.io.FileInputStream;
    import java.io.InputStream;
    
    public class Demo01 {
        public static void main(String[] args) {
            InputStream in = new FileInputStream("C:/temp/ddd");
        }
    }
    複製程式碼

    處理方式一:try...catch

    package com.shxt.demo01;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.InputStream;
    
    public class Demo01 {
        public static void main(String[] args) {
            try {
                InputStream in = new FileInputStream("C:/temp/ddd");
            } catch (FileNotFoundException e) {
                e.printStackTrace();//在控制檯輸出錯誤資訊,給開發人員使用
                //異常出現後,需要如何處理的程式碼
            }
        }
    }
    
    複製程式碼

    處理方式二:throws

    package com.shxt.demo01;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.InputStream;
    
    public class Demo01 {
        public static void main(String[] args) throws FileNotFoundException {//沒有對異常進行處理,只是返回給呼叫者
            InputStream in = new FileInputStream("C:/temp/ddd");
        }
    }
    複製程式碼
  • Runtime異常(執行時異常)

    可處理可不處理的異常(RuntimeException):執行時異常的特點是 Java 編譯器不會檢查它,也就是說,當程式中可能出現這類異常,即使沒有用 try-catch 語句捕獲它,也沒有用 throws 子句宣告丟擲它,也會編譯通過。

    package com.shxt.demo01;
    
    public class Demo02 {
        public static void main(String[] args)  {
           String[] array = {"1","0","abc"};
            try{
                int a = Integer.parseInt(array[0]);
                //int b = Integer.parseInt(array[3]);//IndexOutOfBoundsException
                //int b = Integer.parseInt(array[2]);//NumberFormatException
                int b = Integer.parseInt(array[1]);//NumberFormatException
                int c = a/b;//當b是0的時候,ArithmeticException
                System.out.println("您輸出的結果是"+c);
            }catch(IndexOutOfBoundsException ie){
                System.out.println("陣列越界,輸入的引數不夠");
            }
            catch(NumberFormatException ne){
                System.out.println("數字格式異常:程式只能接收整數引數");
            }
            catch(ArithmeticException ae){
                System.out.println("算術法異常");
            }
            catch(Exception e){
                System.out.println("出現異常");
            }
    
        }
    }
    複製程式碼

    程式說明:

    程式中一般將Exception放在最後,先捕獲小異常(子類異常),再捕獲大異常。如果順序顛倒,還會出現編譯錯誤

4.捕獲異常 try、catch 和 finally

(1) try...catch

語法結構:

try{
  // 需要被檢測的程式碼
}catch(異常類 變數){
 // 處理方式
}catch(異常類 變數){
 // 處理方式
}
複製程式碼

語句說明:

  • try ... catch 是最常見的異常捕獲語句,try就是對你程式碼不自信,O(∩_∩)O哈哈~,也就是業務程式碼,如果try塊中出現問題或者異常,系統自動生成一個異常物件,該異常物件提交到Java執行環境,java執行環境收到異常物件後,會尋找能夠處理該異常的catch塊,如果找到,就將異常物件交給該catch塊處理,如果沒有找到,就終止程式執行。

  • catch塊中如何處理異常:參考上面的執行時異常的示例理解下面的一段話

    一個try塊之後可能存在多個catch塊,java執行時與catch塊()內的異常類進行比較,判斷該異常是否 instanceof 該類,如果屬於該類,就將該異常物件傳給catch塊內的異常形參,catch塊後可以對該異常進行處理,獲取相關異常的詳細資訊等。注意系統生成的異常例項物件是相對具體的子類異常物件,而進入一個catch塊後就不會再進入下一個catch塊,所以這也是我們儘量將小異常放在大異常前面的原因。

在Throwable類的主要方法:輸出出現異常提示資訊

編號 方法名稱 型別 描述
1 public String getMessage() 普通方法 返回詳細描述字串(常用)
2 public void printStackTrace() 普通方法 異常名稱、異常資訊、異常出現位置,程式設計師使用
3 public void printStackTrace(PrintStream s) 普通方法 跟蹤棧資訊輸出到指定輸出流
4 public StackTraceElement[] getStackTrace() 普通方法 返回跟蹤棧資訊,在J2EE的時候有可能用到

好好理解下面的一段話

採用別的替選資料或方案或者提示使用者重新操作或者重新丟擲異常,進行異常轉譯,重新包裝,

交給上層呼叫者來對該異常進行處理,我開發過程中經常使用這種方式處理一些業務邏輯的判斷

捕獲多個異常的JDK7後的新寫法

package com.shxt.demo01;

public class Demo03 {
    public static void main(String[] args)  {
       String[] array = {"1","0","abc"};
        try{
            int a = Integer.parseInt(array[0]);
            //int b = Integer.parseInt(array[3]);//IndexOutOfBoundsException
            //int b = Integer.parseInt(array[2]);//NumberFormatException
            int b = Integer.parseInt(array[1]);//NumberFormatException
            int c = a/b;//當b是0的時候,ArithmeticException
            System.out.println("您輸出的結果是"+c);
        }catch(IndexOutOfBoundsException | NumberFormatException | ArithmeticException e){
            e.printStackTrace();
        }catch(Exception e){
            System.out.println("出現異常");
        }

    }
}
複製程式碼

(2) try...catch...finally

語法結構:

try{
  // 需要被檢測的程式碼
}catch(異常類 變數){
 // 處理方式
}catch(異常類 變數){
 // 處理方式
}finally{
  //一定會執行
}
複製程式碼

或者

try{
  // 需要被檢測的程式碼
}finally{
  //一定會執行
}
複製程式碼
  • try 塊: 用於捕獲異常。其後可接零個或多個 catch 塊,如果沒有 catch 塊,則必須跟一個 finally 塊。
  • catch 塊: 用於處理 try 捕獲到的異常。
  • finally 塊: 無論是否捕獲或處理異常,finally 塊裡的語句都會被執行。當在 try 塊或 catch 塊中遇到 return 語句時,finally 語句塊將在方法返回之前被執行。在以下 4 種特殊情況下,finally 塊不會被執行:
    • 在 finally 語句塊中發生了異常。
    • 在前面的程式碼中用了 System.exit() 退出程式。
    • 程式所在的執行緒死亡。
    • 關閉 CPU。
package com.shxt.demo01;

public class Demo04 {
    public static void main(String[] args)  {
       String[] array = {"1","0","abc"};
        try{
            int a = Integer.parseInt(array[0]);
            //int b = Integer.parseInt(array[3]);//IndexOutOfBoundsException
            //int b = Integer.parseInt(array[2]);//NumberFormatException
            int b = Integer.parseInt(array[1]);//NumberFormatException
            int c = a/b;//當b是0的時候,ArithmeticException
            System.out.println("您輸出的結果是"+c);
        }catch(IndexOutOfBoundsException | NumberFormatException | ArithmeticException e){
            e.printStackTrace();
        }catch(Exception e){
            System.out.println("出現異常");
        }finally{
            array[0] = "999";
            System.out.println("First element value: " +array[0]);
            System.out.println("The finally statement is executed");
        }

    }
}

複製程式碼

5.throws:宣告丟擲異常

package com.shxt.demo01;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

public class Demo01 {
    public static void main(String[] args) throws FileNotFoundException {//沒有對異常進行處理,只是返回給呼叫者
        InputStream in = new FileInputStream("C:/temp/ddd");
    }
}
複製程式碼
  • throws宣告丟擲異常,在方法簽名中使用,上面的Demo01就是其使用的例子。它可以宣告丟擲多個類,多個類之間用“,”隔開。

  • 我們為什麼要宣告丟擲異常?

    • 當某個方法中程式的執行可能會出現異常,但是該方法並不知道如何處理異常
    • 我們想把這個異常交給上層方法呼叫者來處理或者修復,那我們給該方法加上關鍵字throws 異常,以宣告該方法可能會出現的異常
    • 加了throws關鍵字之後,該方法我們就無需再用try—catch來捕獲異常了,因為這已經不是我們這個方法需要操心的事情了
  • 使用throws宣告異常的時候,涉及到子類對父類的方法重寫時,子類宣告的異常型別應該是父類方法宣告的異常型別的子類或者相同類

  • (該段話可以忽略)如果throws 宣告的是checked異常,根據checked異常的規定,我們不能對該異常視而不見,因為我們必須處理該異常,所以當拿到一個宣告瞭可能會發生checked異常的方法時,在呼叫該方法時,要麼放在try塊中來顯式捕捉該異常,要麼放在另外一個帶throws宣告異常的方法中。

    package com.shxt.demo01;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    
    public class Demo05 {
        public static void main(String[] args)throws Exception{
            test();//test 宣告會產生checked 異常 因此main函式也需要宣告異常
            // 或者在try - catch 中捕獲該異常
        }
        public static void test() throws IOException {
            FileInputStream fis = new FileInputStream("a.text");
        }
    }
    複製程式碼
  • (記住這句話)推薦使用Runtime異常,將Checked異常轉換為Runtime異常

    package com.shxt.demo01;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    
    public class Demo06 {
        public static void main(String[] args){
            //根據業務情況,看看是否要處理執行異常
            try {
                test();
            }catch (RuntimeException e){
                System.out.println(e.getMessage());
            }
    
        }
        public static void test() {
            try {
                FileInputStream fis = new FileInputStream("a.text");
            } catch (FileNotFoundException e) {
                e.printStackTrace();//這句話不是處理異常
                throw new RuntimeException("檔案不存在");
            }
        }
    }
    複製程式碼

6.throw:丟擲異常

java允許程式自行丟擲異常,通常系統幫助我們檢查是否發生一些普遍定義的異常,但是有些異常可能不是普遍定義的,只是與我們業務不符,所以我們可以自行丟擲異常,也可以自行丟擲一些我們自定義的異常,而丟擲異常的行為與系統丟擲異常的行為一定程度上是等價的,後續處理方式也是一樣的,在這裡我們使用throw關鍵字。

throw語句可以單獨使用,注意它丟擲的是一個異常例項

  • 當我們自行丟擲的異常是checked異常的時候,該throw語句要麼是在如上面例子中的try塊中,顯示捕獲,要麼是在一個已經用throws宣告會出現異常的方法中

    package com.shxt.demo01;
    
    public class Demo07 {
        public static void main(String[] args)  {
           int a = -1;
           try {
               if(a<0){
                   throw new Exception("資料小於零,可以單獨使用");
               }
           }catch (Exception e){
               e.printStackTrace();
           }
        }
    }
    複製程式碼
  • 如果我們丟擲的是runtime異常,那麼情況就很簡單了,它無需在try塊中,也不需要將對應的方法用throws宣告,如果我們想要處理,就捕獲處理它,不管是在它自身方法體內,或者是對應方法者,也可以不去理會,當然我們自己丟擲的異常,通常情況下是要處理的,不然丟擲去之後不管最後只能中斷程式執行了,只不過丟擲是runtime異常時,在編譯時沒有那麼嚴格

    package com.shxt.demo01;
    
    public class Demo07 {
        public static void main(String[] args)  {
           int a = -1;
    
           if(a<0){
               throw new RuntimeException("資料小於零,可以單獨使用");
           }    
        }
    }
    複製程式碼

自定義異常類:系統會丟擲一些普遍意義的異常,那麼我們也就沒必要再自己操心throw了,通常throw的是自定義的異常類。

  • 自定義異常類都應該繼承Exception類或者Exception下的子類如RuntimeException異常(開發中大部分是基礎RuntimeException)

  • 定義異常類的時候需要提供兩個構造器,一個無參構造器,一個帶一個字串引數的構造器,這串字串實際上是getMessage() 時返回的異常物件的詳細描述資訊。

    package com.shxt.demo01;
    public class MyException extends RuntimeException {
        public MyException() {
            super();
        }
        public MyException(String message) {
            super(message);
        }
    }
    複製程式碼

    catch中throw(丟擲)異常:有時候我們在本方法中捕捉了異常,我們只能處理異常的一部分,我們還需要別的方法來處理或者我們想把產生了異常的這個資訊告訴呼叫者,這個時候我們通常捕捉了異常後會在catch塊中丟擲我們想丟擲的異常 在企業級應用中,通常對異常處理分為兩部分:應用後臺列印或者通過日誌記錄異常發生時詳細情況(異常跟蹤棧)和向使用者傳達某種提示。(這種思想我經常使用)

7.異常使用的注意事項

  • 異常捕獲後不做任何處理,就是耍流氓,挖坑埋自己
  • 異常機制不要用來做流程或條件控制,因為處理效率較低(這句話不太贊同)
  • try-catch若不觸發catch是不影響效能的,但是try塊仍然不要濫用包裹大量程式碼
  • 方法出錯該拋異常就拋異常,而不是返回一些錯誤碼

下面的規則來源網路

http://www.importnew.com/27964.html

  • 在 Finally 清理資源或者使用 Try-With-Resource 特性
  • 優先明確異常
  • 記錄指定的異常
  • 使用描述性訊息丟擲異常
  • 優先捕獲最具體的異常
  • 不要捕獲 Throwable 類
  • 不要忽略異常

相關文章