Java 中的異常

fondtiger發表於2021-09-09

前段時間集合的整理真的是給我搞得心力交瘁啊,現在可以整理一些稍微簡單一點的,搭配學習 ~

 

突然想到一個問題,這些東西我之前就整理過,現在再次整理有什麼區別嘛?我就自問自答一下,可能我再次整理會看到不一樣的一面,會從原始碼和整體來看。其次,之前的整理都是在某個地方止步,可持續性較差,現在就盡力堅持住。因為還有你們再看 ~ 另外,歡迎各行各業的同學們給我投稿,讓大家都能互相瞭解,豈不是很棒!

 

進入正題,看看 Java 中的異常處理。當程式執行的時候沒有按照我們的意思執行,發生了一些不可言語的 bug 時,你打算怎麼搞?不管不問,還是主動出擊,而 Java 語言為此就準備了一套處理異常的類。

 

圖片描述

 

Throwable 類是 Java 語言中所有錯誤或異常的超類。只有當物件是此類(或其子類之一)的例項時,才能透過 Java 虛擬機器或者 Java throw 語句丟擲。類似地,只有此類或其子類之一才可以是 catch 子句中的引數型別。

 

兩個子類,Error 和 Exception,通常用於指示發生了異常情況。通常,這些例項是在異常情況的上下文中最新建立的,因此包含了相關的資訊(比如堆疊跟蹤資料)。Throwable 包含了其執行緒建立時執行緒執行堆疊的快照。還可以給出錯誤提示資訊。

 

Error 及其子類表示的是錯誤,是 JVM 本身出錯了,不能由程式控制,我們最主要就是看異常。

 

Exception 及其子類表示的是異常,異常又分為檢查異常和非檢查異常,這是在 javac 編譯器的角度進行分類的,檢查異常在我們編寫程式碼的時候會提示出來,我們必須要對其進行處理,處理方式有兩種,下面看。非檢查時異常對應於上圖中的 RuntimeException ,編譯的時候不報錯,在執行的時候才會出現。

 

在 Java 中處理異常有兩種思路,自己解決掉,乾淨利索或是拋給方法的呼叫者,就像你有事會找警察一樣,當然警察不一定能給你解決,就像是你丟擲的異常可能會被再次丟擲去一樣。最終會拋給 JVM ,然後程式掛掉。

 

自己處理主要是使用 try { } catch ( ) { } finally { } 語句塊,使用 try 包裹可能出現異常的程式碼,使用 catch 來捕獲可能出現的異常。最後 finally 語句塊是用來執行那些必須要執行的程式碼,比方說釋放資原始檔,關閉資料庫連線等操作。

 

有幾點需要說明一下:

 

1 可以有多個 catch 語句塊,但是捕獲異常的順序一定要注意,當異常出現時,只要被 catch 捕獲,則下面的 catch 不再執行。所以我們要把範圍小的異常種類或者說子類放在上面。

 

2 我們都聽過 finally 語句塊是一定會執行的,其實不是這樣的,當遇到 system.exit() 就不會再執行了,因為你強制退出 JVM 了。除此之外,確實是一定執行的。

 

3 只有 catch 塊才能處理異常,try 只是圈起範圍的作用,而 finally 塊是善後作用。

 

4 try catch finally 語句塊中的變數都是區域性變數,它們之間是不能共享的,待會看個例子。

 

另一種方式就是自己搞不定就使用 throws 丟擲去,拋給方法的呼叫者,讓它們去處理。在方法的宣告中新增語句 throws ,即可將檢查時異常丟擲,後續的處理就留給方法的 caller 吧!

 

上面都是在檢查異常發生時我們可以做的相應處理,若是執行時發生了異常,還有一種操作可以在程式碼執行的過程中手動的丟擲一個異常,使用 throw 關鍵字,例如這樣:

 

String name;if(name == "null"){    throw new IllegalArgumentException("姓名不能為空");
}

 

因為異常的產生是會形成連鎖反應,一旦有異常發生,所有的呼叫者都會發生異常,異常發生最開始的地方就是異常的丟擲點,丟擲點的異常會向 caller 轉移。所以在方法的重寫中,子類丟擲的異常型別一定要小於或等於父類的丟擲異常型別,不然,你想想,子類 throws 出去的異常太大,父類肯定接不住呀。

 

最後就是要看一個例子,來看一下 return 語句在語句塊中的應用。加深一下對異常處理的理解。

 

圖片描述

class Test {    public int aaa() {        int x = 1;        try {            return ++x;
        } catch (Exception e) {

        } finally {            ++x;
        }        return x;
    }    public static void main(String[] args) {
        Test t = new Test();        int y = t.aaa();
        System.out.println(y);
    }
}

圖片描述

 

問:最後列印 y 是多少 ? 執行的過程是怎麼的 ?

 

結果是 2 ,過程卻可能不是那麼好解釋,在 try 語句中我們執行 ++x 然後就直接返回 2 ? 肯定不是,因為還要執行 finally 語句塊啊,所以 Java 規範規定,在存在 finally 塊時,會將 try 中的 return 放在一邊,而優先執行 finally 中的程式碼,但是已經得到的結果 2 可沒有丟了,而是存在了 try 塊中的本地變數表中,以備不時之需。

 

在執行 finally 塊之後,x 等於 3 ,一樣的還是會將 3 儲存在 finally 塊的本地變數表中,然後再回到 try 中進行 return 操作,此時,方法結束,返回的結果就是之前儲存在 try 塊本地變數表中的 2 。假如我們在 finally 塊中也有 return 語句,就會返回 3 而不再執行 try 中的 return 語句,然而 Java 規範不希望我們這麼做,因為 finally 語句塊的主要作用是為了釋放資源,關閉連線操作。

 

結合這個例子我們要明白,在每個語句塊中都會存在一個本地表量表,會儲存使用到變數的值,各個語句塊是分離開的,所以在執行 return 語句的時候,返回的是本地變數表中的變數。

 

在 finally 塊中使用 return 還有一個弊端,它會抑制 try 和 catch 中的異常的丟擲。看下面例子,在 try 塊中也是一樣的。

 

圖片描述

public static void main(String[] args) {        int result;        try{
            result = foo();
            System.out.println(result);           //輸出100
        } catch (Exception e){
            System.out.println(e.getMessage());    //沒有捕獲到異常}    //catch中的異常被抑制
    @SuppressWarnings("finally")    public static int foo() throws Exception {        try {            int a = 5/0;            return 1;
        }catch(ArithmeticException amExp) {            throw new Exception("我將被忽略,因為下面的finally中使用了return");
        }finally {            return 100;
        }
    }    //try中的異常被抑制
    @SuppressWarnings("finally")    public static int bar() throws Exception {        try {            int a = 5/0;            return 1;
        }finally {            return 100;
        }
    }

圖片描述

 

總結

return 語句代表方法結束,但是遇到 finally 語句塊也是要等一等的。

 

無論 try 裡執行了 return 語句、break 語句、還是 continue 語句,finally 語句塊還會繼續執行,除非遇上了 system.exit() 。

 

finally 中不建議使用 return 語句的,釋放資源就好。

原文出處:https://www.cnblogs.com/YJK923/p/9528060.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2508/viewspace-2812575/,如需轉載,請註明出處,否則將追究法律責任。

相關文章