當異常發生時,原本要接著執行的程式碼不再執行,轉而讓其他部分的程式碼來處理。如果沒有程式碼負責處理,控制檯會報告異常。
異常出現時的執行機制:
異常機制最大的好處是:清晰地分開了 正常的業務邏輯 和 遇到情況時的處理 程式碼。(當在業務邏輯中,有多步可能會丟擲不同的異常時,異常處理機制的好處更得以體現。如果沒有這種機制,也許會通過很多的if...else...來實現異常處理,甚至是多層巢狀的if...else...,這樣的程式碼可讀性很差)
通過例子來理解:
package exception; public class ExceptionCatch {
public static void main(String[] args) {
try {
//我們通過丟擲異常來抽象真實的業務邏輯,可能某一步會出現異常,這時下面的程式碼就不再執行,轉而到處理程式碼。
//執行完處理程式碼之後不會再回到之前try裡沒執行完的程式碼繼續執行,而是去往下執行try{}catch{}之後的程式碼。
throw new NullPointerException();
throw new ArrayIndexOutOfBoundsException();
throw ...
}catch(NullPointerException e) {
//異常處理程式碼
}catch(ArrayIndexOutOfBoundsException e) {
//異常處理程式碼
}catch(Exception e){
//異常處理程式碼
}
}
}
-----------------------------------------------------
捕捉到異常之後怎麼處理?
當catch到異常之後,根據你的業務邏輯來對它進行處理,比如說可以彈出一個視窗來警告使用者發生了錯誤,或者讓程式自己重新執行或者終止掉等等。當然,如果現在處理不了這個異常,也可以將它再次丟擲。
處理完異常之後是回不到異常發生的地方繼續執行以下的程式碼了,而是從catch{}後面的程式碼開始執行。
一個異常只能被捕捉一次,捕捉之後這個異常就沒有了。不可能再次捕捉到。
-----------------------------------------------------
自定義異常類的構造器 與 getMessage()方法的使用 :
package exception; //注意觀察兩個異常類構造器的區別。 /**
* 推斷:
* 第一個異常類是自己定義了一個String變數,在構造的時候是將資訊傳給了這個變數;
* 而第二個異常類的構造器是覆蓋了父類的構造方法,所以我們猜測,父類Exception裡一定有一個String型別的成員變數(這個成員變數同樣繼承給了MyException子類),
* 所以在構造的時候利用super()將父類的構造方法取過來,進而將資訊傳給了父類裡的那個String成員變數。
* 而getMessage()方法也是從父類那裡繼承來的,進而我們推斷:getMessage()方法返回的正是那個String變數。
* 在第一個異常類的那個從父類繼承的String變數並沒有被賦值,所以通過getMessage()取到的是null。
*/
class MyException extends Exception {
String msg;
public MyException(String msg) {
this.msg = msg;
}
public void printMsg() {
System.out.println("msg = " + msg);
}
} class MyException2 extends Exception {
public MyException2(String s) {
super(s);
}
} public class E04_ExceptionClass {
public static void main(String args[]) {
try {
throw new MyException("MyException message");
} catch(MyException e) {
e.printMsg();
System.out.println("e.getMessage() = " + e.getMessage()); //輸出為null。
} try {
throw new MyException2("MyException2 message");
} catch(MyException2 e) {
System.out.println("e.getMessage() = " + e.getMessage());
}
}
}
/*Output:msg = MyException message
* e.getMessage() = null
* e.getMessage() = MyException2 message
*/
-----------------------------------------------------
printStackTrace()方法:堆疊跟蹤
我們知道在程式程式碼執行的過程中,某個主執行緒可能會呼叫其他的程式碼程式,呼叫執行之後,回來繼續執行主執行緒。這個過程中就需要堆疊來儲存呼叫時的斷點。(因為在巢狀呼叫的時候會需要儲存多個斷點,返回的時候再倒序依次返回,這要遵循後進先出的原則。)
堆疊跟蹤便可以看成是對程式碼呼叫的跟蹤,當異常發生時可以根據該異常的printStackTrace()方法,打印出該異常的整個傳遞過程。
-----------------------------------------------------
一個方法裡如果會丟擲異常,則必須在方法頭部宣告 throws 異常名 。如果不宣告則必須在這個方法裡通過 try{}catch{} 將異常處理掉。
執行時刻的異常,如ArrayIndexOutOfBoundsException,NullPointerException等,不需要在方法頭部宣告。(Java核心技術裡稱這些未unchecked Exception,未檢查異常。)
一個方法可以宣告多個異常丟擲 throws 異常1 , 異常2 ,但是在以後呼叫該方法時必須有多個catch來捕捉不同的異常。溫馨提示:往往在程式設計時,會把所有可能會有的異常(現在會有的和以後可能會有的)全都宣告在方法之後,這是一種習慣。因為在後期在對該方法進行功能擴充套件時可能會遇到出現這些異常,而當時在宣告這些異常之後編譯器會提示你將這些異常一一catch,所以這時你只需要填補catch裡的處理內容,這會來帶一定的便利。
下面是一個示例:
package exception; class OpenException extends Throwable {}
class CloseException extends Throwable {} public class c {
public static int open() {
return -1;
} public static void readFile() throws OpenException,CloseException {
if(open() == -1) throw new OpenException();
} public static void main(String[] args) {
try {
readFile();
} catch (OpenException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CloseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
-----------------------------------------------------
異常捕捉時的匹配
- 子類異常可以被捕捉父類異常的catch捕捉
- 如果有多個匹配的異常,會按書寫的順序來,順序裡的第一個匹配的catch來捕捉。
-----------------------------------------------------
繼承關係下異常的宣告
- 覆蓋一個方法時,子類方法宣告的異常版本不能比父類方法所宣告的多。
- 子類的建構函式中,必須宣告父類宣告可能丟擲的全部異常。