第九章 例外處理

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

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本身若有繼承關係, 則需先撰寫子類別, 再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()印出來

throws 修飾子

方法的最後面, 可以加throws修飾子, 如
public static void main(String [] args) throws Exception{}

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

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

以上, 方法修飾子是子類別IOException, 但程式裏頭確丟出Exception(父類別), 這就大錯特錯了
就好比丁春秋, 可以丟出弟子當暗器. 但弟子功力不足, 抓不到師父來丟

自訂例外

先使用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

多個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:"性別錯誤";
  }

發佈留言

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