例外處理

      在〈例外處理〉中尚無留言

例外處理在 OCP 的考試屬於 Exceptions and Assertions 範圍。

目的

  • 定義 Java 例外處理
  • 使用 try 及 throw
  • 使用 catch,多重 catch,finally
  • Autoclose resources with a try-with-resources statement
  • 分辨一般例外處理及分類
  • 建立自訂例外處理及 auto-closeable resources
  • Test invariant by using assertions

發生錯誤時的處置

應用程式都會發生錯誤,所以需要有溫和的處理方式。錯誤發生的時機如資料庫無法連線、硬碟無法讀取時。

錯誤歸類為如下幾種類型 :

  • 語法錯誤 : 編譯時期就會發現
  • 執行時期錯誤 : 如除以0, 或陣列超出邊界等
  • 商業邏輯錯誤 : 如餘額為負數等, 這個就不好除錯了。

例外類別分類

Throwable 為最上層的例外處理類別,再由此產生二個子類別

  • Error : 系統本身發出的錯誤
  • Exception : 執行時期所產生的錯誤

Exception

  • RuntimeException : ArithmeticException, NullPointerException, IndexOutOfBoundsException
  • IOException : FileNotFoundException
  • ClassNotFoundException
  • SQLException
  • InterruptedException

Error 及 RuntimeException 屬 unChecked Exception (非檢驗),不強迫要寫 try-catch,因為這類型錯誤都是屬系統資源不足,或其他邏輯錯誤等。

其他灰色的部份如 IOException,都屬 CheckedException(檢驗),一定要寫 try-catch 攔截錯誤。

exception

Java 的例外處理

Handling : 必需增加一段阻斷程式碼來處理錯誤,使用try-catch。
Declaring : 必需在方法宣告如果失敗時的應對方式,使用throws。

    try {
        InputStream in=new FileInputStream("miss.txt");
        System.out.println("File Open");//上面如果錯誤, 此行就不會執行
    } catch (FileNotFoundException ex) {
        System.out.println("無此檔案");
    }

上述架構中,try 區塊只能有一個,但 catch 可以有多個。多 catch 若有繼承關係,則需先撰寫子類別,再寫 父類別。

    try{...}
    catch(FileNotFoundException e){}
    catch(IOException e){}
    catch(Exception e){}

雖然 RuntimeException 不強迫用 try..catch,但也可以手動撰寫

    try{
        int a=number/0;
    }
    catch(ArithmeticException e){
        e.printStackTrace(System.out);
    }
    catch(ArrayIndexOutOfBoundsException e){}

*ArithmeticException 及 ArrayIndexOutOfBoundsException 並無直系繼承,所以那個先寫都可以

*整數除以 0 會發生錯誤,但浮點數是可以除以 0 的,結果是無限大。

finally區塊

finally 區塊一定會被執行,執行的時機是在跳離 try..catch 之前。不管程式有沒有例外,甚至下達了 return,都會執行。

除非下達了System.exit(0),不然就是中斷或關機

注意 :

    try{}
    finally{}

沒有 catch 是可以的。

Call Stack

系統丟出例外事件,try-catch 就會接收事件進行處理。

但如果 try-catch 沒收到(比如丟Exception, 但只 catch IOException),或者是根本就沒寫 try-catch,直接交由方法的 throws 修飾子再往上丟,一直到最後VM接收,這種機制叫Call Stack。

throw 丟出例外

throw new Exception(“發生例外了”);
裏面的字串可被接收的 catch 接收,用 e.getMessage() 印出錯誤的 log。

throws 修飾子

方法的最後面可以加 throws 修飾子,如

public static void main(String [] args) throws Exception{}

注意 : throw 所丟出的例外必需是 throws 的子類別,如下是錯誤的

    public void test() throws IOException{
        throw new Exception();
    }

上述的方法修飾子是子類別 IOException,但程式裏頭確丟出 Exception(父類別)。程式丟出的例外範圍太大,但方法修飾子 throws 的手套太小無法接收。

自訂例外

先使用 extends 產生自訂例外類別 LoginException。login 方法加入修飾子 throws LoginException,方法中  throw new LoginException(),再於呼叫 login() 的地方用 try-catch 包含起來。

class LoginException extends Exception{
    public LoginException(){
        super("帳號或密碼錯誤!!");
    }
}
public class ExceptionTest {
    public static void main(String[] args) {
        try{
            login("thomas", "12345");
        } catch (LoginException ex) {
            System.out.println(ex.getMessage());
        }
    }
    public static void login(String account, String password) throws LoginException{
        if (!account.equals("thomas") || !password.equals("123456")){
            throw new LoginException();
        }
    }
}

方法有例外時該如何覆寫

父類別的方法若有 throws 例外,那子類別要如何覆寫呢??
1. 可以不用寫
2. 子類別方法的例外, 必需跟原方法一樣, 或是原方法例外的子類別

class Pokemon{
    public void getWeight() throws Exception{}
    public void getHeight() throws Exception{}
    public void getLevel() throws IOException{}
}
class Pikachi extends Pokemon{
    public void getWeight(){}
    public void getHeight() throws IOException{}
    public void getLevel() throws Exception{}//error
}

上述的getLevel()就會產生編譯錯誤

多個catch

Java 允許多個 catch,且若有繼承關係,需先寫子類別, 再寫父類別。

但如果是下面的狀況呢!!

    try{
        login("thomas", "12345");
    } 
    catch (FileNotFoundException e) {
        System.out.println(e.getMessage());
    }
    catch(IOException e){
        System.out.println(e.getMessage());
    }

紅色的部份重複了,所以又改成如下

    try{
        login("thomas","1234");
    } 
    catch(IOException e){
        System.out.println(e.getMessage());
    }

FileNotFoundException 是 IOException 的子類別,所以沒問題。

但如果一個是 SQLException,一個是IOException,二者沒有繼承關係。在 Java SE 6 只能乖乖寫二次,
但 Java SE 7 可以使用如下賤招

    try{
         int i=10;
         if(i==10)throw new IOException(); 
         else throw new SQLException();
    } 
    catch(IOException | SQLException e){ 
         System.out.println(e.getMessage()); 
         //e = new Exception(); //error 
    }

注意 : 此時 e 是 final 的,不能變更其值,也就是 e = new Exception(); 會產生編譯錯誤

注意 : | 左右二邊例外不能有繼承的關係。

例外重丟(Rethrowing)

以下的第一個程式,可以改良為第二個

    第一個
    public static void main(String[] args) throws SQLException, IOException {
        if(Math.random()<0.5)throw new SQLException();
        else throw new IOException();
    }

   第二個
    public static void main(String[] args) throws SQLException, IOException, Exception {
        try{
            if(Math.random()<0.5)throw new SQLException();
            else throw new IOException();
        }
        catch(Exception e){
            throw e;
        }
    }

上面叫重丟,丟是丟的很高興,但 main 的 throws 卻是要重改。

不過 Java SE 7 就有改良(只是覺的有點脫褲子放屁了),不用重改 main 的 throws,因為他知道是誰丟上來的,會把 Exception 轉成 SQLException or IOException,如下所示。

    public static void main(String[] args) throws SQLException, IOException{
        try{
            if(Math.random()<0.5)throw new SQLException();
            else throw new IOException();
        }
        catch(Exception e){
            throw e;
        }
    }

try-with-resources

撰寫 try-catch 時需要在 finally 寫上關閉資源檔(如資料庫檔, 文字檔)的程式碼。自 Java SE 7 之後,可以把開檔寫在try () 裏面,然後編譯器就會自動在 finally 填上關閉的程式碼。

    try(FileWriter fw=new FileWriter("temp.txt");
       FileReader fr=new FileReader("text.txt")) {
    }
    catch(IOException e){}

上述的物件,需實作 java.lang.AutoCloseable 介面才可以使用。
實作AutoCloseable介面,需實作close()方法

class Pokemon implements AutoCloseable{
    public void doSomething(){
        System.out.println("執行中");
    }
    @Override
    public void close(){
        System.out.println("close");
    }
}
public class ExceptionTest {
    public static void main(String[] args){
        try(Pokemon p=new Pokemon()){
        }
    }
}

以上的 close() 會由編譯器自動產生 finally 區塊,然後執行 close 方法。
若在 close() 裏面有丟出例外,則 main 裏面除了 try 外,還要再加上 catch

class Pokemon implements AutoCloseable{
    public void doSomething(){
        System.out.println("執行中");
    }
    @Override
    public void close() throws IOException{
        System.out.println("close");
        throw new IOException("close的例外");
    }
}
public class ExceptionTest {
    public static void main(String[] args){
        try(Pokemon p=new Pokemon()){
        }
        catch(IOException e){
            System.out.println(e.getMessage());
        }
    }
}

try 區塊若丟出例外,則實作 AutoCloseable 的資源在 close() 裏丟出的例外會被壓制(被吃案了)

class Pokemon implements AutoCloseable{
    public void doSomething(){
        System.out.println("執行中");
    }
    @Override
    public void close() throws IOException{
        System.out.println("close");
        throw new IOException("close的例外");//被吃案了
    }
}
public class ExceptionTest {
    public static void main(String[] args){
        try(Pokemon p=new Pokemon()){
            throw new Exception("try中的例外");
        }
        catch(IOException e){
            //getSuppressed寫在這裏沒有用, 因為這裏根本不會被執行
        }
        catch(Exception e){
            for (Throwable t:e.getSuppressed()){
                System.out.println(t.getMessage());
            }            
            System.out.println(e.getMessage());
        }
    }
}

上述的 close 雖說是丟出 IOException,但被壓制住了,所以 IOException 不會發生,因此把getSuppressed() 寫在 IOException 的 catch 中,沒任何作用。

例外壓制 – 吃案的傢伙

例外太多,造成太過發散,所以先用 addSuppressed() 把案子吃下來,以後再統一吐出來。

public class ExceptionTest {
    public static void main(String[] args){
        Throwable eatCase=new Throwable();
        try{
            throw new Exception("第一次");
        }
        catch(Exception e){
            eatCase.addSuppressed(e);
        }
        try{
            throw new IOException("第二次");
        }
        catch(Exception e){
            eatCase.addSuppressed(e);
        }
        for (Throwable t:eatCase.getSuppressed()){
            System.out.println(t.getMessage());
        }
    }
}

注意 AutoCloseable 裏的 close() 方法所丟出來的例外是會被壓制的。

AssertionError

用來維護程式碼的東西。一時之間也查覺不出是不是有錯,所以就用 assert 來標註一下,等日後再來查看看。這有點像 Android 常用的手法

    private static final boolean DEBUG=true
    if (DEBUG) {
        Log.d(LOG_TAG, "Something");
    }

Java 預設的 Assertion 是關閉的,若要打開需於命令列執行時加上 -ea
java -ea MyTest


語法 : assert expression
比如
boolean flag=false;
assert (flag=true);
當java -ea MyTest時, assert被打開了, 所以flag就會被改成true;

然後在後面的程式碼, 就可以加入哨兵了
if(flag){
…………………..
}


語法 : assert expression1:expression2
expression1 如果是 true 就沒事,如果是 false 就產生 AssertionError,然後把 expression2 的字給印出來


使用時機 :

   assert(score>=0):"成績沒有負的啦";
   if(score>=60){}
   else{}
   寫在迴圈之外 : 如果被執行了, 那表示迴圈破功了
  寫在switch之default中:
  switch(sex){
     case '1': System.out.println("男性");break;
     case '2': System.out.println("女性");break;
     default: assert false:"性別錯誤";
  }

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *