iT邦幫忙

0

【左京淳的JAVA學習筆記】第八章 例外處理

本章重點

  • 例外和例外的處理
  • 例外處理class
  • try-catch-finally
  • throws和throw
  • over write

JAVA的source file即使編譯成功,在執行時也可能出錯,此時的報錯稱為例外。
例外由負責執行程式的JVM發出給使用者知道。如果沒有指定應對方法的話,程式就會直接中斷。

對應例外的class階層如下
Throwable(全部例外處理的父類)

  • Error //記憶體不足等嚴重錯誤,難以由程式進行對應所以直接報error結束。
  • Exception //可對應的例外
    • RuntimeException //直譯就是跑程式時發生的例外,不一定要指定對應方式。
    • 非RuntimeException例外 //又稱作checked例外,必須指定對應方式。通常是外部的檔案或DataBase找不到。

來看看各例外class下面常見的子class

Error class

class 說明
AssertionError assert(條件)返回false時終止程式
OutOfMemoryError 記憶體不足
StackOverflowError 重複迴圈數過多
NoClassDefFoundError 找不到class檔

RuntimeException class

class 說明
ArrayIndexOutOfBoundsException index值超出陣列/列表範圍
ClassCastException 強制轉換物件的class時出錯
IllegalArgument Exception 引數格式不對
ArithmeticException 除數為0
NullPointerException 方法執行時引用到null資料
NumberFormat Exception 資料轉換為整數時報錯

非RuntimeException class

class 說明
IOException 輸入輸出資料時出錯
FileNotFoundException 找不到檔案
ClassNotFoundException 找不到class

除了JAVA內建的例外class,開發者也可以自定義class如下例:

public class MyException extends Exception{}

繼承Exception類可以獲得以下方法

  • void printStackTrace() //顯示例外發生的地方
  • String getMessage() //取得例外發生的原因等相關資訊

例外的處理方式有以下兩種:

  • try-catch-finally
  • throws

先來看看try-catch-finally的文法和範例
文法如下:

void method(){
  try{
    可能發生例外的地方
  }catch(例外處理的class 變數){
    例外發生時如何處理
  }finally{
    不管有沒有例外都要進行的處理
  }
}

使用時未必三種關鍵字都要寫,catch和finally擇一即可。

範例:

class Sample8_1 {
  public static void main(String[] args) {
    int[] num = {10,20,30};
    for (int i = 0; i < 4; i++){
      try{
        System.out.print("num :" + num[i]);
        System.out.println(" : 第" + (i + 1) + "次的迴圈");
      }catch(ArrayIndexOutOfBoundsException e){
        System.out.println("例外發生!");
      }
    }
    System.out.println("--end--");
  }
}

執行結果

num :10 : 第1次的迴圈
num :20 : 第2次的迴圈
num :30 : 第3次的迴圈
例外發生!
--end--

如果把陣列縮短一點,執行結果如下

num :10 : 第1次的迴圈
num :20 : 第2次的迴圈
例外發生!
例外發生!
--end--

添加了finally的範例如下:

class Sample8_2 {
  public static void main(String[] args) {
    int[] num = {10,20,30};
    for (int i = 0; i < 4; i++){
      try{
        System.out.print("num :" + num[i]);
        System.out.println(" : 第" + (i + 1) + "次的迴圈");
      }catch(ArrayIndexOutOfBoundsException e){
        System.out.println("例外發生!");
      }
    }
    System.out.println("--end--");
  }
}

執行結果

num :10 : 第1次的迴圈
--finally--
num :20 : 第2次的迴圈
--finally--
num :30 : 第3次的迴圈
--finally--
例外發生!
--finally--
--end--

catch處理可以指定多個例外處理class,但若class之間有繼承關係,則須從子類到父類(範圍從小到大)撰寫,否則會編譯錯誤(因為不合邏輯)。
會報錯的範例:

}catch(Exception e){
  ...
}catch(NullPointerException e){
  ...
}

正確的範例

}catch(NullPointerException e){
  ...
}catch(Exception e){
  ...
}

若是沒有繼承關係的class,則可以"|"符號組合起來。

}catch(FileNotFoundException | ArithmeticException e){

用throws傳遞例外

有時候遇到例外時我們不想當場處理,可以使用throws來傳遞給上一層或更上層,最後再用try-catch來處理它。
請看以下範例:

class DBAccess{
  void select(...){
    try{
      ...
    }catch{
      ...
    }
  }
  void insert(...){
    try{
      ...
    }catch{
      ...
    }
  }
}

上面是一個訪問DB的class,裡面有複數的方法存在。如果每一個都要寫try-catch的話,程式碼會變得很冗長。
可以利用throws改寫為以下樣子

class DBAccess{
  void select(...) throws SQLException{
  }
  void insert(...) throws SQLException{
  }
}

然後在呼叫出此物件的上層,利用try-catch進行處理

class Test{
  DBAccess d = new DBAccess();
  try{
    d.select(...);
    d.insert(...);
  }catch(SQLException e){
    // get log
  }
}

如上例,如果物件裡有throws方法的話,使用這些方法時就必須使用try-catch包覆起來。
JAVA裡面的工具類也有這樣的案例,例如:
java.io包提供的FileReader類的建構式,發生例外時會throws FileNotFoundException。
這個例外不屬於RuntimeException的子類,所以"必須"指定例外處理方法。
java.lang包提供的Integer類的parseInt()方法出錯時會throws NumberFormatException
這個例外屬於RuntimeException的子類,所以不一定要指定例外處理方法。

關於是不是一定要指定例外處理方法,請看以下範例:

import java.io.*;

class Sample8_2 {
  public static void main(String[] args) {
    //不一定要處理的例外
    int i = Integer.parseInt("10");
    
    //一定要處理的例外
    FileReader r = new FileReader("Test.txt");
}

編譯結果

Sample8_2.java:9: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
    FileReader r = new FileReader("Test.txt");
                   ^
1 error

必須處理的例外,如果沒有用try-catch包覆,編譯時就會報錯。

用throw手動拋出例外

throw和前面說的throws是不一樣的指令,請看清楚了。
有些時候我們想要自己定義什麼樣的值是正常的,什麼樣的值是異常值需要進行例外處理。
這時候就可以使用throw指令丟出例外並終止後續的處理。

class MyException extends Exception {
  private int age;
  public void setAge(int age){this.age = age;}
  public int getAge(){return this.age;}
}
class Sample8_3 {
  public static void main(String[] args) {
    try{
      int age = -10;
      checkAge(age);
    }catch(MyException e){
      System.out.println("年齡異常。age:" + e.getAge());
    }
  }
  
  public static void checkAge(int age) throws MyException{
    if (age >= 0){ 
      System.out.println("OK");
    }else{
      MyException e = new MyException();
      e.setAge(age);
      throw e;
    }
  }
}

執行結果

年齡異常。age:-10

覆寫具有throws的方法時,須注意的規則:

當我們要寫一個子類來繼承父類,並覆寫具有throws的方法時,須注意以下規則:
子類方法可以跟父類丟出一樣的例外處理,或是不寫throws,或是改寫為符合以下規則的處理

  1. 比之前的例外處理範圍更小的例外處理(也就是其子類)。可以想像子類應該是要比父類更精確地描述例外。
  2. RuntimeException

來看看範例:

import java.io.*;

class Super{ void method() throws IOException{} }

class SubA extends Super { void method() {} }
class SubB extends Super { void method() throws FileNotFoundException {} }
class SubC extends Super { void method() throws Exception{} }
class SubD extends Super { void method() throws ClassNotFoundException{} }
class SubE extends Super { void method() throws RuntimeException{} }

編譯結果

class SubC extends Super { void method() throws Exception{} }
                                ^
  overridden method does not throw Exception
Sample8_4.java:8: error: method() in SubD cannot override method() in Super
class SubD extends Super { void method() throws ClassNotFoundException{} }
                                ^
  overridden method does not throw ClassNotFoundException
2 errors

說明
SubA沒有寫throws,OK
SubB拋出FileNotFoundException,是IOException的子類,OK。
SubC拋出Exception範圍大於Super的IOException因此報錯
SubD拋出ClassNotFoundException與 Super的IOException沒有繼承關係,而且也不是RuntimeException,所以報錯。
SubE拋出RuntimeException,OK。

以上 是關於JAVA的學習筆記,到此結束。之後會整理JAVA Web的學習筆記(雖然還很菜..希望整理得出來)

參考教材: JAVAプログラマSilver SE8 - 山本 道子


尚未有邦友留言

立即登入留言