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