Java 異常(一) 異常概述及其架構

學無止境發表於2020-06-03

Java 異常(一) 異常概述及其架構

一、異常概述

(一)、概述

Java異常是Java提供的一種識別及響應錯誤的一致性機制。異常指的是程式在執行過程中,出現的非正常的情況,最終會導致JVM的非正常停止。
Java異常機制可以使程式中異常處理程式碼和正常業務程式碼分離,保證程式程式碼更加優雅,並提高程式健壯性。在有效使用異常的情況下,異常能清晰的回答 what, where, why 這3個問題:異常型別回答了“什麼”被丟擲,異常堆疊跟蹤回答了“在哪“丟擲,異常資訊回答了“為什麼“會丟擲。

(二)、異常體系

異常機制其實是幫助我們找到程式中的問題,異常的根類是java.lang.Throwable,其下有兩個子類:java.lang.Errorjava.lang.Exception,平常所說的異常指java.lang.Exception

 

 Throwable: 有兩個重要的子類:Exception(異常)和 Error(錯誤),二者都是 Java 異常處理的重要子類,各自都包含大量子類。異常和錯誤的區別是:異常能被程式本身可以處理,錯誤是無法處理。

1、Throwable:Throwable是 Java 語言中所有錯誤或異常的超類。Throwable包含兩個子類: Error 和 Exception。它們通常用於指示發生了異常情況。

Throwable包含了其執行緒建立時執行緒執行堆疊的快照,它提供了printStackTrace()等介面用於獲取堆疊跟蹤資料等資訊。

Throwable常用API:

public void printStackTrace() // 列印異常的詳細資訊。包含了異常的型別,異常的原因,還包括異常出現的位置。
public String getMessage() // 獲取發生異常的原因。
public String toString() // 獲取異常的型別和異常描述資訊。

2、Error:嚴重錯誤Error,無法通過處理的錯誤,只能事先避免。

3、Exception:表示異常,異常產生後程式設計師可以通過程式碼的方式糾正,使程式繼續執行,是必須要處理的。

(三)、異常分類

我們平常說的異常就是指Exception,因為這類異常一旦出現,我們就要對程式碼進行更正,修復程式。

異常主要分為兩大類:編譯期異常和執行期異常。

編譯期異常:checked異常。在編譯期,就會檢查,如果沒有處理異常,則編譯失敗(如日期格式化異常)。

特點: Java編譯器會檢查它。此類異常,要麼通過throws進行宣告丟擲,要麼通過try-catch進行捕獲處理,否則不能通過編譯。例如,CloneNotSupportedException就屬於被檢查異常。當通過clone()介面去克隆一個物件,而該物件對應的類沒有實現Cloneable介面,就會丟擲CloneNotSupportedException異常。被檢查異常通常都是可以恢復的。

執行期異常:runtime異常。在執行時期,檢查異常.在編譯時期,執行異常不會編譯器檢測(不報錯)(如數學異常)。

特點: Java編譯器不會檢查它。也就是說,當程式中可能出現這類異常時,倘若既"沒有通過throws宣告丟擲它",也"沒有用try-catch語句捕獲它",還是會編譯通過。例如,除數為零時產生的ArithmeticException異常,陣列越界時產生的IndexOutOfBoundsException異常,fail-fail機制產生的ConcurrentModificationException異常等,都屬於執行時異常。

雖然Java編譯器不會檢查執行時異常,但是我們也可以通過throws進行宣告丟擲,也可以通過try-catch對它進行捕獲處理。

如果產生執行時異常,則需要通過修改程式碼來進行避免。例如,若會發生除數為零的情況,則需要通過程式碼避免該情況的發生!

二、異常處理

Java異常處理機制用到的幾個關鍵字:try、catch、finally、throw、throws

  • try:用於監聽。將要被監聽的程式碼(可能丟擲異常的程式碼)放在try語句塊之內,當try語句塊內發生異常時,異常就被丟擲。
  • catch:用於捕獲異常。catch用來捕獲try語句塊中發生的異常。
  • finally:finally語句塊總是會被執行。它主要用於回收在try塊裡開啟的物力資源(如資料庫連線、網路連線和磁碟檔案)。只有finally塊,執行完成之後,才會回來執行try或者catch塊中的return或者throw語句,如果finally中使用了return或者throw等終止方法的語句,則就不會跳回執行,直接停止。
  • throw:用於丟擲異常。
  • throws:用在方法簽名中,用於宣告該方法可能丟擲的異常。

三、異常注意點

(一)、finally中的異常會覆蓋(消滅)前面try或者catch中的異常

  • 不要在fianlly中使用return。
  • 不要在finally中丟擲異常。
  • 減輕finally的任務,不要在finally中做一些其它的事情,finally塊僅僅用來釋放資源是最合適的。
  • 將盡量將所有的return寫在函式的最後面,而不是try ... catch ... finally中。

(二)、多個異常使用捕獲

  • 多個異常分別處理。
  • 多個異常一次捕獲,多次處理。
  • 多個異常一次捕獲一次處理。

一般情況下,一般我們是使用一次捕獲多次處理方式,如下程式碼

try{
     // 編寫可能會出現異常的程式碼
}catch(異常型別A  e){  當try中出現A型別異常,就用該catch來捕獲.
     // 處理異常的程式碼
     //記錄日誌/列印異常資訊/繼續丟擲異常
}catch(異常型別B  e){  當try中出現B型別異常,就用該catch來捕獲.
     // 處理異常的程式碼
     //記錄日誌/列印異常資訊/繼續丟擲異常
}

注意:這種異常處理方式,要求多個catch中的異常不能相同,並且若catch中的多個異常之間有子父類異常的關係,那麼子類異常要求在上面的catch處理,父類異常在下面的catch處理。

三、例項

(一)、try - catch用法

try - catch 必須搭配使用,不能單獨使用。

public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            int i = 10 / 0;// 除數不能為0,此行會丟擲 ArithmeticException 異常
            System.out.println("i = " + i);// 丟擲異常後,此行不會執行
        }catch(ArithmeticException e) { // 捕獲 ArithmeticException 異常
            // 在catch 程式碼塊處理異常
            e.printStackTrace(); // 異常最詳細資訊
            System.out.println("e.getMessage() : " + e.getMessage());// 發生異常的原因
            System.out.println("e.toString() : " + e.toString());  // 獲取異常的型別和異常描述資訊
        }
    }
}

(二)、finally 用法

try - catch - finally搭配使用,或者 try - finally 搭配使用。

public class ExceptionDemo {
    public static void main(String[] args) {
        // try-catch-finally搭配使用
        try {
            int[] arr = {1,2,3};
            int i = arr[3];// 陣列索引越界,此行會丟擲 ArrayIndexOutOfBoundsException 異常
            System.out.println("i = " + i);// 丟擲異常後,此行不會執行
        }catch(ArithmeticException e) { // 捕獲 ArithmeticException
            System.out.println(e.getMessage());// 發生異常的原因
            System.exit(0); // 程式強制退出,finally 程式碼塊不會執行
        }finally {// 除了程式強制退出,如(System。exit(0)),無論是否發生異常,finally 程式碼塊總會執行
            System.out.println("this is finally");
        }
        
        // try-finally搭配使用
        try {
            int[] arr = {1,2,3};
            int i = arr[3];// 陣列索引越界,此行會丟擲 ArrayIndexOutOfBoundsException 異常
            System.out.println("i = " + i);// 丟擲異常後,此行不會執行
        }finally { 
            // 無論是否發生異常,finally 程式碼塊總會執行
            System.out.println("this is finally");
        }
    }
}

注意點:

  • try-catch-finally 搭配:這種形式捕獲異常時,開發者可以在 catch 程式碼塊中處理異常(如列印日誌、日誌記錄等),異常處理權在開發者。
  • try-finally 搭配:這種形式捕獲異常時,預設丟擲給 JVM 處理,JVM預設處理時呼叫 e.printStackTrace() 方法列印異常詳細資訊。
  • finally 程式碼塊:除非程式強制退出,否則無論程式是否發生異常,finally 程式碼塊總會執行。
  • finally 中丟擲異常會覆蓋(消滅)前面 try 或者 catch 中的異常,儘量避免在 finally 程式碼塊中丟擲異常。
  • 如果 try 中和 finally 中都有 return 語句,程式會先執行 finally 中的 return 語句,然後程式塊執行結束,而不會執行 try 中的 return 語句。所以儘量不在finally中使用 return 語句。

(三)、throw 用法

throw 是用於丟擲異常,將這個異常物件傳遞到呼叫者處,並結束當前方法的執行

public static void main(String[] args) {
  try {
    int i = 10 / 0;
    System.out.println("i = " + i);
  }catch(ArithmeticException e) { 
    // 丟擲異常,傳遞自定義異常資訊提示
    // 預設丟擲給 JVM 處理列印異常詳細資訊    
    throw new ArithmeticException("除數不能為0");
  } 
}

(四)、throws 用法

throws運用於方法宣告之上,用於表示當前方法不處理異常,而是提醒該方法的呼叫者來處理異常(丟擲異常)。

public class ExceptionDemo {
    public static void main(String[] args) {
        demo();
    }
    public static void demo() throws ArrayIndexOutOfBoundsException{
        try {
            int[] arr = {1,2,3};
            int i = arr[3];
            System.out.println("i = " + i);
        }catch(ArrayIndexOutOfBoundsException e) { 
           System.out.println(e.toString());
        } 
    }
}