java零基礎入門-高級特性篇(九) 異常 中

上一節講到了檢查異常,這種必須處理的異常到底該怎麼處理呢?通常的處理方式就是捕獲異常或者拋出異常,捕獲異常就是在異常出現的時候當場解決,而拋出異常則是把鍋甩出去,把異常往上層拋出,讓上層邏輯來解決它。處理異常有專門的關鍵字,java中的異常家族裡有以下幾種關鍵字,try、catch、finally、throw、throws,下面來分別介紹它們。

捕獲異常

捕獲異常就是當場就地正法,使用try和catch關鍵字來處理異常。try用來監視代碼邏輯的運行,如果沒有異常,那麼程序會一直運行到結束,而一旦發生異常,並且在try的監控範圍之內,那麼程序就會跳轉到catch部分,運行catch裡面的代碼。如果沒有捕獲異常,程序會直接結束,所以捕獲異常可以給我們一次挽救程序異常停止的機會,就算不能挽救,也至少可以知道為什麼程序會出現異常。

try-catch

上面這個try-catch結構就是基本的捕獲異常結構,try後面的程序就是正常的邏輯代碼,catch後面是如果發生了異常需要執行的代碼。需要注意的是,在出現異常以後,不會繼續執行程序,而是直接跳到catch部分執行代碼,所以這裡輸出完第一個列印語句以後就馬上輸出了異常信息。

e是Exception的對象,調用Exception父類Throwable的方法printStackTrace(),輸出異常信息,輸出異常信息有多種方式,printStackTrace()是一種,這種方式輸出的是最詳細的錯誤信息,包括出現異常代碼的行號,異常信息和異常原因,這種方式適合調試程序,找到錯誤。還有一種getMessage(),這種輸出只會輸出異常的信息,比上面一種方法輸出的信息要少,這種方式適合記錄日誌,將錯誤的信息作為日誌記錄下來,以便需要的時候排查問題。

多個異常的捕獲結構

上面的例子是使用Exception捕獲的異常,其實理論上來說,應該使用最準確的異常來捕獲,由於Exception是所有異常的父類,所以使用Exception沒有問題,但是最適當的方式是使用FileNotFoundException來捕獲這裡的異常。為什麼要用子類來捕獲異常?因為使用子類捕獲異常可以將異常處理的更加精細,比如下面這個例子。(裡面流和反射的知識可能沒有學到,但是此處只需關注異常即可)。

使用子類捕獲

當一段邏輯中有可能出現多個異常需要捕獲的時候,如果直接使用Exception,那麼只能執行一個異常邏輯,而不能將不同的異常區分開。這樣就導致無法根據不同的異常進行不同的異常處理。當一段邏輯中出現多個需要捕獲的異常的時候,可以在try後面接多個catch,分別對不同的異常類型進行捕獲。

同時使用

Exception和Exception的子類在捕獲異常的時候是不衝突的,但是子類的捕獲必須在父類之前,如果第一個catch的是Exception,那麼他會直接捕獲所有異常,不能單獨處理其他異常了。異常捕獲的順序是按照異常出現的順序來的,如果首先出現的是文件找不到異常,那麼會被FileNotFoundException捕獲,如果首先出現的是ClassNotFoundException,那麼會被Exception捕獲。

有finally的結構

講完try和catch關鍵字以後,再來看另一個關鍵字finally。在處理異常的時候,try關鍵字是必須出現的,有了try關鍵字,程序才會在try所包含的代碼塊中捕獲異常,而catch和finally是可以任意出現一個的,也可以兩個同時出現。finally的特性是,不論在catch中是否出現異常,finally中的代碼都會被執行。因為有一些代碼如果寫在try中,如果出現異常,那麼這些代碼是可能不會被執行的,如果寫在catch中,如果不發生異常也不會執行,所以需要一個地方來寫無論是否出現異常都會被執行的代碼。

finally

finally在處理資源的時候非常有用,比如IO,網路,數據連接等等,因為在使用這些資源的時候需要在代碼中手動的回收,但是如果發生異常就不會執行到回收資源的代碼,所以在finally中回收資源是一種很好的選擇。

使用finally需要注意的幾個地方:

1.如果有一個或多個catch關鍵字的話,finally要出現在最後一個catch之後。順序如果有錯誤會發生編譯錯誤。

2.不建議在finally裡面使用return關鍵字。

finally中的return

在finally裡面是可以使用return關鍵字的,但是會導致結果與預期不符合。比如上面這個例子,上例中其實是不檢查異常,可以捕獲也可以不捕獲,這裡為了說明finally就捕獲異常了。這裡預期返回的是兩個參數的商,程序運行到try中的return是不會馬上結束方法的,因為後面有finally語句,而finally語句中也有return,最後的結果就是finally中的return導致try中的return無效。無論程序是否發生異常,方法預期返回的結果都被改變了,返回的不是程序希望得到的兩個參數的商,而是一個與參數無關的字元串,所以通常不建議在finally中使用return關鍵字。

final ,finally 和finalize

這個地方要指出的是,這幾個關鍵字八竿子打不著關係,但是經常會有外行題目問這幾個關鍵字有什麼區別。這裡簡單說一下。

final 定義的變數,初始化變數後不可修改。final定義的方法不可以被覆寫。final定義的類不可以繼承。

finally用於異常結構,不論是否發生異常,都會運行finally中的代碼。

finalize用於定義垃圾回收器應該執行的操作。

拋出異常

捕獲異常講完了,輪到拋出異常了。前面說了檢查異常,有沒有想過,為什麼檢查異常就必須處理呢?因為在定義類,方法的時候,源碼已經將異常拋出了,所以你在使用類的時候就必須處理它,要麼捕獲,要麼拋出。前面例子中

FileOutputStream out = new FileOutputStream(file);

會有一個FileNotFoundException類型的異常必須處理,來看看FileOutputStream這個類的構造器。

拋出異常

什麼是拋出異常?

拋出異常就是遇到檢查異常,並沒有捕獲異常直接處理,而是將異常交給調用方處理。

為什麼要拋出異常而不是直接捕獲?

因為設計上的需要。當我們在寫一個業務的時候,碰見異常最好的方法就是捕獲並處理它。但是如果寫的是一個公共的工具方法或者是父類,抽象類等需要將業務進行抽象的時候,並不能預見到具體的業務是什麼,所以不能直接給出解決方案,這時候就需要將異常交給調用方,在使用者具體使用的時候,再來捕獲該異常,根據具體情況確定具體的處理方式。

異常具體是怎麼拋出的?

異常的拋出

首先在一個需要拋出異常的地方將異常往上一級(方法的調用者)拋出,然後上一級還可以繼續往上一級拋出,如果到最後都沒有被捕獲,該異常會被拋給jvm,jvm也沒法處理異常只能把異常信息列印出來。

這個過程就像出了問題,開始甩鍋一樣。方法A出了問題,自己可能沒有辦法處理,就把鍋甩給了方法B,方法B一看這個我也沒法解決啊,轉手又甩了出去,最後這個鍋被甩給了老大哥JVM,JVM老大哥看到異常也只能幹瞪眼,沒有辦法最後只能把異常信息列印出來,誰寫的代碼誰來認領一下,錯誤給你看了,自己想辦法去解決。

拋出異常

java使用關鍵字 throws 拋出異常,throws後面跟上異常的類型,跟catch的捕獲類型差不多,定義什麼類型的異常就會拋出什麼類型的異常,如果直接拋出Exception,那麼就是拋出所有的異常類型。跟catch可以捕獲多種異常類型一樣,throws也可以拋出多種異常類型,這樣就可以讓上一級的代碼根據不同的異常類型分別進行處理。如果只拋出Exception類型的異常,上一級就無法對異常進行精確的控制了。

拋出多個異常

使用throws同時拋出多個異常的時候,使用逗號將多個異常分開。throws這種拋出異常的方式可以看做是一種被動式拋異常,因為throws拋出的異常可能發生也可能不發生,java中除了throws拋出異常,還有一種主動式拋異常,下面來看看什麼是主動式拋異常。

throws 和 throw

主動拋出異常的關鍵字是throw。和throws只差了一個小寫字母s,這裡需要重點區分開兩種異常拋出方式的區別。

throws:1)拋出的是類,在方法後面寫的是異常的類名 2)可以同時拋出多種類型異常 3)throws拋出的異常不一定會發生 4)在方法名處拋出

throw:1)拋出的是異常類的實例 2)只能拋出一種異常 3)拋出的異常一定會發生 4)在方法內部拋出

throw用在拋出不檢查異常的情況比較多。使用throw可以將代碼的邏輯補充的更加完整,因為某些異常在特定的情況是需要根據業務邏輯來判斷是否拋出,在特定的情況下是可以確定異常的,而不是像throws不確定是否會出現異常。這種情況下就可以使用throw在方法體中拋出異常。

throw

上例中,假設用戶需要輸入兩個數字,然後計算兩個數字的商。用戶輸入是不確定的,但是一旦用戶將intTest2輸入為0,代碼邏輯可以確定這裡肯定會有一個異常,那麼可以直接使用throw來拋出這個異常。一旦在調用方法時捕獲到該異常,也可以確定異常的信息,比如上例中可以將捕獲到的信息直接反饋給用戶,第二個數不能為0。

需要注意的是throw只是拋出異常的方式比較靈活,可以在代碼邏輯中拋出異常,而拋出異常以後,上級的處理邏輯和throws是一樣的,要麼繼續往上級拋異常,要麼捕獲異常。

推薦閱讀:

相关文章