例外處理在 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 攔截錯誤。

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