Java面向對象之異常詳解

Java面向對象之異常【一】

Java面向對象之異常【二】

往期回顧:上一篇咱們大體總結了異常的繼承體系,說明了Exception和Error兩個大類都繼承於頂級父類Throwable,又談到編譯時異常與運行時異常的區別,談到異常的處理方式,以及處理方式中關於捕獲方式的幾種類型。
本篇承上啓下,將從異常的其他部分進行總結,可是畢竟如今處於初學階段,未必可以體會異常在真實場景中運用的便利之處,因此本文只是對目前所學內容的概括整理,後續新的體會將會及時更新。java

捕獲異常的規則

  • 在執行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修飾的子句老是會被執行spa

因而咱們進行了簡單的嘗試:3d

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");code

可是,化名爲幾千萬個爲何的我又開始疑惑了,咱們直到return能夠將方法直接返回,強制退出。那麼若是在try中使用return語句,finally還會不會不忘初心,繼續執行呢?htm

前方高能!各單位注意!!!
猜猜看,這四個方法執行結果是啥呢?

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='巴夏'}

本文如有敘述不當之處,還望評論區批評指正哦!

相關文章
相關標籤/搜索