Java面向對象之異常【一】
Java面向對象之異常【二】
往期回顧:上一篇我們大致總結了異常的繼承體系,說明了Exception和Error兩個大類都繼承於頂級父類Throwable,又談到編譯時異常與運行時異常的區別,談到異常的處理方式,以及處理方式中關於捕獲方式的幾種類型。
本篇承上啟下,將從異常的其余部分進行總結,但是畢竟現在處於初學階段,未必能夠體會異常在真實場景中運用的便利之處,所以本文只是對目前所學內容的歸納整理,后續新的體會將會及時更新。
捕獲異常的規則
- 在執行try塊的過程中沒有出現異常,那么很明顯,沒有異常當然就會跳過catch子句。
- 相反,如果拋出了一個異常,那么就會跳過try中的剩余語句,開始查找處理該異常的代碼。以下是查找處理異常代碼的具體過程:
- 從當前方法開始,沿着方法的調用鏈,按照異常的反向傳播方向找到異常的處理代碼。
- 從第一個到最后一個檢查catch塊,判斷是否相匹配。如果是,那么恭喜!直接進入catch塊中執行處理異常語句;如果不是,就將該異常傳給方法的調用者,在調用者中繼續執行相同步驟:匹配就處理,不匹配就向上傳……
- 直到最后如果都沒有找到的話,程序將會終止,並在打印台上打印出錯信息。
如果是相對單一方法而言,其實是很簡單的;如果方法層層嵌套呢,情況又是咋樣的呢,咱們來驗證一下以上內容:
//主方法
public static void main(String[] args) {
try{
//調用m1()
m1();
System.out.println("ExceptionMyDemo.main");
}catch (Exception e){
System.out.println("ExceptionMyDemo.main.catch");
}
}
//m1()
private static void m1(){
try{
//調用m2()
m2();
System.out.println("ExceptionMyDemo.m1");
}catch (NullPointerException e){
System.out.println("ExceptionMyDemo.m1.catch");
}
}
//m2()
private static void m2(){
String str = null;
System.out.println(str.hashCode());
}
//測試結果:
ExceptionMyDemo.m1.catch
ExceptionMyDemo.main
- 可以看到,m1中捕獲了m2拋出匹配的空指針異常類型,直接處理,在main方法中就接收不到異常,也就正常執行。
- 假如我們把m1的catch的異常類型換成其他的類型,比如
catch (ArithmeticException e)
,這時的測試結果會是這個樣子:
//更改之后的測試結果:
ExceptionMyDemo.main.catch
因為m2()拋出的異常在m1中並沒有被合適地處理,所以向上拋出,在main方法中找到了處理方法,遂執行處理語句。
- 依據我們上面所說,如果在上面更改之后的基礎上,再把main方法中的處理異常語句刪去,那么程序運行的結果會是啥呢?哦,不出所料,是下面的結果:
因為拋出的異常沒人處理,它就會在控制台上打印異常的棧軌跡,關於拋出的異常信息,我們接下來進行詳細分析。
訪問異常信息
我們提到,無論是虛擬機拋出異常還是我們主動拋出,異常的錯誤信息都包含其中,以便於我們得知並更好地處理異常,那么順着上面所說,我們剛剛看到的就是異常的棧軌跡:
public void printStackTrace()
:默認將該Throwable對象及其調用棧的跟蹤信息打印到標准錯誤流。public String getMessage()
:返回描述異常對象信息的字符串。public String toString()
:異常信息message為空就返回異常類的全名,否則返回全名:message
的形式。public StackTraceElement[] getStackTrace()
:返回棧跟蹤元素的數組,表示和該異常對象相關的棧的跟蹤信息。
異常對方法重寫的影響
- 異常對方法重載沒有影響。
- 方法重寫時,子類中重寫的方法拋出的編譯時異常不能超過父類方法拋出編譯時異常的范圍。
finally詳解
名言警句:無論異常是否會發生,finally修飾的子句總是會被執行。
於是我們進行了簡單的嘗試:
public static void m2(){
try{
System.out.println("0");
System.out.println(1/0);
System.out.println("1");
}
catch (Exception e){
System.out.println("2");
}
finally {
System.out.println("3");
}
System.out.println("4");
}
//測試結果
0 2 3 4 被打印在控制台上
-
可以看到1沒有被打印,因為在執行
System.out.println(1/0);
時發生了異常,於是進入catch塊,finally子句必會被執行,然后執行try語句后的下一條語句。 -
想象以下:假如把接收異常的實例類型改為另外一個不匹配的類型的話,也就是說無法正常捕獲,結果又會如何呢?結果如下:
-
很明顯,這時候finally的效果就出來了,就算你出了異常,我finally塊中的語句必須要執行,這個在現實場景中對於釋放資源起了很關鍵的作用,但是具體來說,由於還沒有學習后面的內容,就暫且不提了,有些東西還是體會之后會更加真實一些。
-
還有一個注意點就是4也沒有被打印出來,是因為沒有捕獲到異常,將會把異常拋給調用者,所以不會執行
System.out.println("4");
。
但是,化名為幾千萬個為什么的我又開始疑惑了,我們直到return可以將方法直接返回,強制退出。那么如果在try中使用return語句,finally還會不會不忘初心,繼續執行呢?
前方高能!各單位注意!!!
猜猜看,這四個方法執行結果是啥呢?
private static int m1(){
try{
return 1;
}catch(Exception e){
}
return 2;
}
private static int m2(){
try{
return 1;
}finally {
return 2;
}
//使用finally子句時可以省略catch塊
}
private static int m3(){
try{
return 1;
}finally {
try{
return 2;
}finally {
return 3;
}
}
}
private static int m4(){
int i = 4;
try{
return i++;
}finally {
i++;
}
}
答案揭曉:分別是:1,2,3,4。你們猜對了嗎?哈哈……
我想前三個答案應該是毋庸置疑的,但是這第四個就有點離譜了。不是說finally語句一定會執行嗎,執行哪去了呢,你要是執行的話,你i難道不應該變成6了嗎?
額……咳咳,這個嘛,我也有點迷惑,但是經過一番討教,稍微懂了一些:
- 當執行try之前,如果后面有finally,會將try中的返回過程延遲,就是說把i=4放到結果區。
- 然后在計算區進行自增運算變為5,finally語句一定會執行,但是只是在計算區域自增為6了,結果區域還是原來的那個4。
- 不信的話,你可以在finally語句的i++后面看看i的值,它!就是6!所以說finally子句一定執行是毋庸置疑的的!
但是如果進行改變的是引用數據類型的變量時,那么就會隨之改變了,人家村的是地址,改的就是本身。我在這邊就稍微來個簡單的例子奧:
public static Student m(){
Student s = new Student();
try{
s.age = 20;
s.name = "天喬";
return s;
}finally {
s.name = "巴夏";
s.age = 2;
}
}
//測試結果
//Student{age=2, name='巴夏'}
本文若有敘述不當之處,還望評論區批評指正哦!