異常處理:try-catch、finally與throws
尚硅谷JavaSE筆記-16

異常的分類

在Java中,程序執行中的異常分為ExceptionError,他們都繼承自Throwable

(語法錯誤跟邏輯錯誤那不叫異常)

Error

  • 錯誤,JVM系統內部錯誤虛擬機無法解決的問題、資源耗盡等嚴重情況,比如:

    • 無限迴圈產生堆疊溢位(Stack Overflow)

    • 寫錯分配導致記憶體不足(Out-Of-Memory),

  • 解決方法就是把它寫對

Exception

  • 例外,發生了出乎預料的事,又依"受不受檢"分成

  • Checked Exception:又稱編譯時異常,通常在原始碼中必須顯式地catch並且處理,比如:

    • IOException、讀取文件不存在

    • ClassNotFoundException

    • 這部分在compile time就會檢查

  • Unchecked Exception:又稱RuntimeException,運行時異常,比如:

    • NullPointerException,空指針訪問
    • ArrayIndexOutOfBoundsException,數組角標越界
    • ClassCastException,類型轉換異常
    • NumberFormatException,數字類型不合
    • InputMismatchException,輸入數據不符合,例如scan int結果來了字串
    • ArithmeticException,算法異常,例如把某數除以0
    • 通常是透過撰寫相應程式以避免的邏輯錯誤, 可以根據當下的情境來判斷是不是要catch

異常處理

代碼執行時,一旦出現異常,就會在異常處生成一個對應異常類的物件,並將其拋出,拋出後的代碼就不再執行

try-catch

  • 格式:

    try {
        int num = Integer.parseInt(str);
    } catch (NumberFormatException e) {
        e.printStackTrace(); // 印出異常
    } catch (Exception e) {
        // something
    }
    
  • 使用try{...}將可能有異常的代碼段包起來,當運行中異常發生時,走到哪停在哪,且生成一個異常的物件

  • 使用catch預測可能生成的異常物件類,當有對應的就進入處理(類似於switch-case結構)

  • catch想抓的異常類型,如果有子父類關係,必須從小到大,否則報錯

  • try結構中聲明的變量,只能在try中使用,出了try就不能再被調用

try-catch-finally

  • 格式:

    @org.junit.Test
    public void test1() {
        int n = method();
        System.out.println(n);
    }
    
    public int method() {
        try {
            int[] arr = new int[3];
            System.out.println(arr[5]);
            System.out.println("異常發生之後的語句"); // 不會執行
            return 1;
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("抓到對應的異常類"); // 先執行
            return 2;
        } finally {
            System.out.println("必執行"); // 後執行
            return 3; // 最後真正返回的值
        }
    }
    
  • finally不是必要

  • 當使用finally時,只要真的有異常,則其中的語句必定會被執行,即使前面的異常處理有returnthrow new之類也不管,總之finally就是要墊底走一趟

  • 像是資料庫連接、IO輸出入、網路socket等資源,JVM不能自動回收的,我們需要手動寫資源釋放,就會把這塊放在finally

  • try-catch-finally可以嵌套使用,例如我要關掉資料庫的連接,上面try區包了一個連接用的物件conn,出了try區想在finally區寫"conn.close()“卻編譯報錯不給用,只好再用一個try-catchfinally包起來。舉例:

    FileInputStream file = null;
    try {
        // 操作...
    } catch (FileNotFoundException e) {
    } finally {
        try {
            if (file != null) {
                file.close();
            }
        } catch (IOException e) {
        }
    }
    

throws+異常類型

  • 格式:聲明在方法之後,指明這個方法執行時,可能會拋出的異常類型,舉例:

    public void register(int id) throws Exception,RuntimeException{}
    
  • 拋出的異常類型可以是複數

  • 體會:只是將異常拋給方法的調用者,適合用在方法有層層遞進關係的程式碼中,達成一個集中處理提高可讀性。但拋到最後總要有個接鍋俠去try-catch真正解決問題

  • 如果父類的方法沒有拋出,則子類重寫的方法也不能拋出,只能在原地try-catch

  • 子類重寫的方法,拋出的異常類型不能比父類本來拋出的類型還大(兒子犯的錯,要老爸罩得住)

手動拋出異常

  • 想要手動的把某些狀況讓JVM當作是異常來處理,使用關鍵字**throw new 異常類**,注意沒有s

  • 舉例:

    public class Test {
        public static void main(String[] args) {
            Student s1 = new Student();
            try {
                s1.register(-5);
                // 當異常發生時,不希望以下的代碼被執行,所以在方法手動拋出
                System.out.println("生成成功,ID為" + s1.id);
            } catch (Exception e) {
    //            e.printStackTrace();
                System.out.println(e.getMessage());
            }
        }
    }
    
    class Student {
        public int id;
    
        public void register(int id) throws Exception {
            if (id > 0) {
                this.id = id;
            } else {
                throw new Exception("數據非法!"); // 這邊手動拋
                // 或使用throw new RuntimeException("數據非法!");
                // 就不需要上面方法處的thorws,但就只是單純報錯停下而無處理
            }
        }
    }
    

自定義異常類

舉例:

public class MyException extends RuntimeException {
    // 身分證號
    static final long serialVersionUID = -70348971907457669L;

    // 構造器
    public MyException() {

    }

    public MyException(String msg) {
        super(msg);
    }
}

小結

  • throw:通常跟new一起聲明在方法體內,表示在此必定拋出一個異常類的物件,只要執行到這就拋異常並且撒手不幹了,必須有誰來處理
  • throws:是方法能夠拋出異常的聲明,表明調用此方法時可能拋出的異常種類(可以是複數),誰調用此方法就必須考慮可能發生異常,自己看著辦吧
  • 兩者都是消極的異常處理,只是拋出表明可能拋出異常,真正處理終究是需要一個try-catch去處置

案例比喻

  • 當"小朋友衝到馬路上"時,throw一個異常,停止當前方法

  • public void 照顧小朋友() throws KidsException {
    	if (小朋友衝到馬路上=true) {
    		throw new KidsException("出事了阿北"); // 手動拋出異常
    	} else {
          // 安全,繼續做別的事
    	}
    }
    // KidsException是一個自訂的異常類,繼承自Exception
    // 如果是RuntimeException通常不會顯式的用try-catch處理
    // 運行時異常也不會被要求在方法聲明時顯式的throws
    
  • throws是用來修飾"照顧小朋友"這個方法,表示預期某些異常可能發生

  • 完整的處裡流程:我執行照顧小朋友方法,若發生了異常我就將其catch起來,呼叫父母處理,實際解決異常

  • try {
    	.照顧小朋友();   // 此方法用throws修飾了,表示我預期某些異常可能發生
    } catch (KidsException e) {
    	e.printStackTrace();
    	.呼叫父母(); // 實際解決異常
    }
    

上次修改於 2021-12-01