您現在的位置是:首頁 > 單機遊戲首頁單機遊戲

java菜鳥到大佬——全網最全異常機制講解

  • 由 一個即將退役的碼農 發表于 單機遊戲
  • 2023-01-31
簡介這種異常的特點是Java編譯器會檢查它,也就是說,當程式中可能出現這類異常,要麼用try-catch語句捕獲它,要麼用throws子句宣告丟擲它,否則編譯不會透過

git工具是幹嘛的

一.Java異常的定義

java異常的出現為了識別和響應錯誤而出現的,它是一種一致性機制。

Java異常

的作用

Java異常可以讓我們在程式開發時將業務程式碼和異常處理程式碼分開,提升了程式程式碼的優雅性和健壯性。

What:異常型別告訴我們是什麼異常。

Where:異常的堆疊跟蹤告訴我們具體哪行程式碼發生了異常。

Why:異常資訊告訴我們為什麼會發生異常。

二.Java異常架構

java菜鳥到大佬——全網最全異常機制講解

2.1

Throwable

如上圖所示:Throwable 是所有錯誤和異常的超類。它提供了 printStackTrace() 等介面用於獲取堆疊跟蹤資料等資訊。

異常資訊分成兩大類:Error(錯誤)和 Exception(異常)

2.2

Error(錯誤)

Error 類及其子類:此類錯誤一般是比較嚴重的系統性底層錯誤,在應用程式中你是無法處理的。

在JVM體系中,它一般代表 Virtual MachineError(虛擬機器執行錯誤)、NoClassDefFoundError(類定義錯誤)等。

典型的例子:

OutOfMemoryError:記憶體溢位錯誤;

StackOverflowError:棧溢位錯誤

它們帶來的後果都是強制終止正在執行的執行緒。

針對以上這些定義,當此類錯誤發生時,java程式碼不會去處理,也不會去自定義實現它的子類。

2.3

Exception(異常)

跟error不同,Exception 異常一般是上層程式碼的錯誤,它可以被程式捕獲和處理的。

Exception 分為兩大類:

RuntimeException:執行時異常。

非執行時異常:編譯異常。

2.3.1

執行時異常

RuntimeException顧名思義發生在在JVM執行期間,編譯期不會去檢查它。當你沒有在程式中宣告(throws)或者捕獲(try-catch)它時,程式編譯也是可以透過的。

比較典型的執行時異常主要包括下面幾類:

NullPointerException:空指標異常

ArrayIndexOutBoundException:陣列下標越界異常

ClassCastException:型別轉換異常

ArithmeticExecption:算術異常

綜上所述,執行時異常是有程式邏輯引起的,程式應該從邏輯角度去儘可能避免這類異常的發生。

2.3.2

編譯時異常

「定義」: Exception 中除 RuntimeException 及其子類之外的異常。

「特點」: Java 編譯器會檢查它。如果程式中出現此類異常,比如 ClassNotFoundException(沒有找到指定的類異常),IOException(IO流異常),要麼透過throws進行宣告丟擲,要麼透過try-catch進行捕獲處理,否則不能透過編譯。在程式中,通常不會自定義該類異常,而是直接使用系統提供的異常類。「該異常我們必須手動在程式碼裡新增捕獲語句來處理該異常」。

2.

4. 受檢異常與非受檢異常

Java 的所有異常可以分為受檢異常(checked exception)和非受檢異常(unchecked exception)。

2.4.1

可查的異常(checked exceptions)

正確的程式在執行中,很容易出現的、情理可容的異常狀況。可查異常雖然是異常狀況,但在一定程度上它的發生是可以預計的,而且一旦發生這種異常狀況,就必須採取某種方式進行處理。

除了RuntimeException及其子類以外,其他的Exception類及其子類都屬於可查異常。這種異常的特點是Java編譯器會檢查它,也就是說,當程式中可能出現這類異常,要麼用try-catch語句捕獲它,要麼用throws子句宣告丟擲它,否則編譯不會透過。

2.4.2 不

可查的異常(編譯器不要求強制處置的異常)

包括執行時異常(RuntimeException與其子類)和錯誤(Error)

三.Java異常關鍵字

「try」 – 用於監聽。將要被監聽的程式碼(可能丟擲異常的程式碼)放在try語句塊之內,當try語句塊內發生異常時,異常就被丟擲。

「catch」 – 用於捕獲異常。catch用來捕獲try語句塊中發生的異常。

「finally」 – finally語句塊總是會被執行。它主要用於回收在try塊裡開啟的物力資源(如資料庫連線、網路連線和磁碟檔案)。只有finally塊,執行完成之後,才會回來執行try或者catch塊中的return或者throw語句,如果finally中使用了return或者throw等終止方法的語句,則就不會跳回執行,直接停止。

「throw」 – 用於丟擲異常。

「throws」 – 用在方法簽名中,用於宣告該方法可能丟擲的異常。

四.Java異常處理流程

java菜鳥到大佬——全網最全異常機制講解

當程式在執行的過程中出現了異常,會由JVM自動根據異常的型別例項化一個與之型別匹配的異常類。

2。產生異常物件後會判斷當前的語句是否存在異常處理,如果現在沒有異常處理,就交給JVM進行預設的異常處理,處理方式:輸出異常資訊,而後結束程式的呼叫。

3。如果此時存在異常的捕獲操作,那麼會先有try語句來捕獲產生的異常類例項化物件,再與try‘語句的每一個catch進行對比,如果有符合的捕獲型別,則使用當前catch的語句來進行異常的處理,如果不匹配,則往下繼續匹配其他catch。

4。不管最後異常處理是否能夠匹配,都要向後執行,如果此時程式中存在finally語句,就先執行finally中的程式碼。執行完finally語句後需要根據之前的catch匹配結果來決定如何執行,如果之前已經成功捕獲異常,就集繼續執行finally之後的程式碼,如果之前沒有成功的捕獲異常,九江此異常交給JVM進行預設處理。整個過程就像方法傳遞引數一樣,只是根據catch後面的引數型別進行匹配。既然物件捕獲只是一個異常類物件的傳遞過程,那麼根據java中物件自動向上轉型,所以異常類物件都可以向父類物件轉型,也證明了所有異常類物件都可以使用Exception來接收。

為什麼不選擇Throwable?

如果該異常只有Exception型別,如果使用Throwable接受,還會表示可以處理Error的錯誤,二使用者是處理不了Error錯誤的(jvm處理),所以開發中使用者可以處理的異常都要求以Exception為主。

Java 透過面向物件的方法進行異常處理,一旦方法丟擲異常,系統自動根據該異常物件尋找合適異常處理器(Exception Handler)來處理該異常,把各種不同的異常進行分類,並提供了良好的介面。在 Java 中,每個異常都是一個物件,它是 Throwable 類或其子類的例項。當一個方法出現異常後便丟擲一個異常物件,該物件中包含有異常資訊,呼叫這個物件的方法可以捕獲到這個異常並可以對其進行處理。Java 的異常處理是透過 5 個關鍵詞來實現的:try、catch、throw、throws 和 finally。

在Java應用中,異常的處理機制分為宣告異常,丟擲異常和捕獲異常。

4.1

宣告異常

在Java中,當前執行的語句必屬於某個方法,Java直譯器呼叫main方法執行開始執行程式。若方法中存在檢查異常,如果不對其捕獲,那必須在方法頭中顯式宣告該異常,以便於告知方法呼叫者此方法有異常,需要進行處理。 在方法中宣告一個異常,方法頭中使用關鍵字throws,後面接上要宣告的異常。若宣告多個異常,則使用逗號分割。如下所示:

java菜鳥到大佬——全網最全異常機制講解

通常,應該捕獲那些知道如何處理的異常,將不知道如何處理的異常繼續傳遞下去。傳遞異常可以在方法簽名處使用 「throws」 關鍵字宣告可能會丟擲的異常。

java菜鳥到大佬——全網最全異常機制講解

Throws異常丟擲的原則:

4.2

丟擲異常

如果你覺得解決不了某些異常問題,且不需要呼叫者處理,那麼你可以丟擲異常。

throw關鍵字作用是在方法內部丟擲一個Throwable型別的異常。任何Java程式碼都可以透過throw語句丟擲異常。

在業務程式碼中,如果程式碼可能會產生某種異常,導致程式錯誤,你可以new 一個合適的異常物件例項並丟擲,如下圖所示:

java菜鳥到大佬——全網最全異常機制講解

在微服務架構中,在做系統整合時,當某個子系統發生故障時,異常會有很多種型別,我們可以統一處理並向外暴露,不用把系統具體的異常細節暴露出去。

java菜鳥到大佬——全網最全異常機制講解

4.3

捕獲異常

異常的捕獲有四種方式:

try-catch

try-catch-finally

try-finally

try-with-resource

try-catch

你可以catch多個異常,並對不同型別的異常做出不同的處理,同一個 catch 也可以捕獲多種型別異常,用 | 隔開

java菜鳥到大佬——全網最全異常機制講解

java菜鳥到大佬——全網最全異常機制講解

try-catch-finally

常規

寫法

java菜鳥到大佬——全網最全異常機制講解

執行

步驟

· 當try沒有捕獲到異常時:try語句塊中的語句逐一被執行,程式將跳過catch語句塊,執行finally語句塊和其後的語句;

· 當try捕獲到異常,catch語句塊裡沒有處理此異常的情況:當try語句塊裡的某條語句出現異常時,而沒有處理此異常的catch語句塊時,此異常將會拋給JVM處理,finally語句塊裡的語句還是會被執行,但finally語句塊後的語句不會被執行;

· 當try捕獲到異常,catch語句塊裡有處理此異常的情況:在try語句塊中是按照順序來執行的,當執行到某一條語句出現異常時,程式將跳到catch語句塊,並與catch語句塊逐一匹配,找到與之對應的處理程式,其他的catch語句塊將不會被執行,而try語句塊中,出現異常之後的語句也不會被執行,catch語句塊執行完後,執行finally語句塊裡的語句,最後執行finally語句塊後的語句;

java菜鳥到大佬——全網最全異常機制講解

Java程式碼示例

java菜鳥到大佬——全網最全異常機制講解

try-finally

try塊中如果發生異常,異常程式碼之後的語句不會執行,直接執行finally中的語句。 try塊沒有引發異常,則執行完try塊然後執行finally語句。

使用場景:用在不需要捕獲異常的場景,可以保證資源在使用後被關閉。

典型案例:io流的釋放;Lock釋放;資料庫連線關閉;

java菜鳥到大佬——全網最全異常機制講解

Q:finally程式碼塊一定會執行嗎?

A: finally程式碼塊在下面這些情況下會停止執行:

如果你在程式碼中使用System。exit()退出了程式。

如果finally語句塊中發生了異常。

如果程式所在的執行緒終止和死亡。

關閉CPU。

try-with-resource

上面例子中,finally塊中的方法也可能會丟擲 IOException異常, 這種情況下,我們的業務異常就被覆蓋了。

別慌,JAVA 7 為我們提供了更優雅的方式來實現資源的自動釋放,自動釋放的資源需要是實現了 AutoCloseable 介面的類。

程式碼實現

java菜鳥到大佬——全網最全異常機制講解

看下Scanner

java菜鳥到大佬——全網最全異常機制講解

try 程式碼塊退出時,會自動呼叫 scanner。close 方法,和把 scanner。close 方法放在 finally 程式碼塊中不同的是,若 scanner。close 丟擲異常,則會被抑制,丟擲的仍然為原始異常。被抑制的異常會由 addSusppressed 方法新增到原來的異常,如果想要獲取被抑制的異常列表,可以呼叫 getSuppressed 方法來獲取。

如何選擇異常型別

可以根據下圖來選擇是捕獲異常,宣告異常還是丟擲異常

java菜鳥到大佬——全網最全異常機制講解

異常基礎總結

try、catch和finally都不能單獨使用,只能是try-catch、try-finally或者try-catch-finally。

try語句塊監控程式碼,出現異常就停止執行下面的程式碼,然後將異常移交給catch語句塊來處理。

finally語句塊中的程式碼一定會被執行,常用於回收資源 。

throws:宣告一個異常,告知方法呼叫者。

throw :丟擲一個異常,至於該異常被捕獲還是繼續丟擲都與它無關。

Java程式設計思想一書中,對異常的總結。

在恰當的級別處理問題。(在知道該如何處理的情況下了捕獲異常。)

解決問題並且重新呼叫產生異常的方法。

進行少許修補,然後繞過異常發生的地方繼續執行。

用別的資料進行計算,以代替方法預計會返回的值。

把當前執行環境下能做的事儘量做完,然後把相同的異常重拋到更高層。

把當前執行環境下能做的事儘量做完,然後把不同的異常拋到更高層。

終止程式。

進行簡化(如果你的異常模式使問題變得太複雜,那麼用起來會非常痛苦)。

讓類庫和程式更安全。

五.常見異常處理方式

直接丟擲異常

通常在方法內我們無法處理的異常,或者需要使用者關注的異常,都需要往外傳遞。即在方法上使用「throws」 關鍵字宣告丟擲的異常

java菜鳥到大佬——全網最全異常機制講解

封裝異常再丟擲

有時候為了業務,我們需要對特定的多種異常做特殊處理。這個時候就需要去將原生異常catch並封裝成業務異常丟擲。

java菜鳥到大佬——全網最全異常機制講解

自定義異常

習慣上,建立一個RuntimeException的子類,並設定兩個構造器,一個無參,一個有參,Throwable 的 toString 方法會列印這些詳細資訊,除錯時很有用。

java菜鳥到大佬——全網最全異常機制講解

六.Java異常常見面試題

6.

1. Error 和 Exception 區別是什麼?

①。

Exception(異常)

是應用程式中可能的可預測、可恢復問題。一般大多數異常表示中度到輕度的問題。異常一般是在特定環境下產生的,通常出現在程式碼的特定方法和操作中。在 EchoInput 類中,當試圖呼叫 readLine 方法時,可能出現 IOException 異常。

Exception 類有一個重要的子類 RuntimeException。RuntimeException 類及其子類表示“JVM 常用操作”引發的錯誤。例如,若試圖使用空值物件引用、除數為零或陣列越界,則分別引發執行時異常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。

②。

Error(錯誤)

表示執行應用程式中較嚴重問題。大多數錯誤與程式碼編寫者執行的操作無關,而表示程式碼執行時 JVM(Java 虛擬機器)出現的問題。例如,當 JVM 不再有繼續執行操作所需的記憶體資源時,將出現 OutOfMemoryError。

檢查異常 和 未檢查異常 的劃分

6.

2. 執行時異常和一般異常(受檢異常)區別是什麼?

Java提供了兩類主要的異常:runtime exception和checked exception。checked 異常也就是我們經常遇到的IO異常,以及SQL異常都是這種異常。對於這種異常,JAVA編譯器強制要求我們必需對出現的這些異常進行catch。所以,面對這種異常不管我們是否願意,只能自己去寫一大堆catch塊去處理可能的異常。

但是另外一種異常:runtime exception,也稱執行時異常,我們可以不處理。當出現這樣的異常時,總是由虛擬機器接管。比如:我們從來沒有人去處理過NullPointerException異常,它就是執行時異常,並且這種異常還是最常見的異常之一。

出現執行時異常後,系統會把異常一直往上層拋,一直遇到處理程式碼。如果沒有處理塊,到最上層,如果是多執行緒就由Thread。run()丟擲,如果是單執行緒就被main()丟擲。丟擲之後,如果是執行緒,這個執行緒也就退出了。如果是主程式丟擲的異常,那麼這整個程式也就退出了。執行時異常是Exception的子類,也有一般異常的特點,是可以被Catch塊處理的。只不過往往我們不對他處理罷了。也就是說,你如果不對執行時異常進行處理,那麼出現執行時異常之後,要麼是執行緒中止,要麼是主程式終止。

如果不想終止,則必須撲捉所有的執行時異常,決不讓這個處理執行緒退出。佇列裡面出現異常資料了,正常的處理應該是把異常資料捨棄,然後記錄日誌。不應該由於異常資料而影響下面對正常資料的處理。在這個場景這樣處理可能是一個比較好的應用,但並不代表在所有的場景你都應該如此。如果在其它場景,遇到了一些錯誤,如果退出程式比較好,這時你就可以不太理會執行時異常,或者是透過對異常的處理顯式的控制程式退出。

6.

3. JVM 是如何處理異常的?

java菜鳥到大佬——全網最全異常機制講解

在編譯生成的位元組碼中,每個方法都附帶一個異常表。異常表中的每一個條目代表一個異常處理器,並且由from指標,to指標,target指標以及所捕獲的異常型別構成。這些指標的值是位元組碼索引,用以定位位元組碼。

其中,from指標和to指標標識了該異常處理器所監控的範圍,例如try程式碼塊所覆蓋的範圍。target指標則指向異常處理器的起始位置,例如catch程式碼塊的起始位置。

當程式觸發異常時,Java虛擬機器會從上至下遍歷異常表中的所有條目。當觸發異常的位元組碼的索引值在某個異常表條目的監控範圍,Java虛擬機器會判斷所丟擲的異常和該條目想要捕獲的異常是否匹配。如果匹配,Java虛擬機器會將控制流轉移至該條目target指標指向的位元組碼。

如果遍歷完所有異常表條目,Java虛擬機器仍未匹配到異常處理器,那麼它會彈出當前方法對應的Java棧幀,並且在呼叫者中重複上述操作。在最壞情況下,Java虛擬機器需要遍歷當前執行緒Java棧上所有方法的異常表。

針對異常執行路徑,Java編譯器會生成一個或多個異常表條目,監控整個try-catch程式碼塊,並且捕獲所有種類的異常。這些異常表條目的target指標將指向另一份複製的finally程式碼塊。並且,在這個finally程式碼塊的最後,Java編譯器會重新丟擲所捕獲的異常。

可見,一共有三份finally程式碼塊。其中,前兩份分別位於try程式碼塊和catch程式碼塊的正常執行路徑出口。最後一份則作為異常處理器,監控try程式碼塊以及catch程式碼塊。它將捕獲try程式碼塊觸發的未被catch程式碼塊捕獲的異常,以及catch程式碼塊觸發的異常。

有個小問題?如果catch程式碼塊捕獲了異常,並且出發了另一個異常,那麼finally捕獲並且重拋的異常是哪個呢?答案是後者。也就是所原本的異常便會被忽略掉,不易於程式碼除錯。

6.

4. throw 和 throws 的區別是什麼?

Java 中的異常處理除了包括捕獲異常和處理異常之外,還包括宣告異常和丟擲異常,可以透過 throws 關鍵字在方法上宣告該方法要拋出的異常,或者在方法內部透過 throw 丟擲異常物件。

「throws 關鍵字和 throw 關鍵字在使用上的幾點區別如下」:

1、throws出現在方法函式頭;而throw出現在函式體。

2、throws表示出現異常的一種可能性,並不一定會發生這些異常;throw則是丟擲了異常,執行throw則一定丟擲了某種異常物件。

3、兩者都是消極處理異常的方式(這裡的消極並不是說這種方式不好),只是丟擲或者可能丟擲異常,但是不會由函式去處理異常,真正的處理異常由函式的上層呼叫處理。

好的程式設計習慣:

1。在寫程式時,對可能會出現異常的部分通常要用try{。。。}catch{。。。}去捕捉它並對它進行處理;

2。用try{。。。}catch{。。。}捕捉了異常之後一定要對在catch{。。。}中對其進行處理,那怕是最簡單的一句輸出語句,或棧輸入e。printStackTrace();

3。如果是捕捉IO輸入輸出流中的異常,一定要在try{。。。}catch{。。。}後加finally{。。。}把輸入輸出流關閉;

4。如果在函式體內用throw丟擲了某種異常,最好要在函式名中加throws拋異常宣告,然後交給呼叫它的上層函式進行處理。

6.

5. final、finally、finalize 有什麼區別?

final可以修飾類、變數、方法,修飾類表示該類不能被繼承、修飾方法表示該方法不能被重寫、修飾變量表示該變數是一個常量不能被重新賦值。

finally一般作用在try-catch程式碼塊中,在處理異常的時候,通常我們將一定要執行的程式碼方法finally程式碼塊中,表示不管是否出現異常,該程式碼塊都會執行,一般用來存放一些關閉資源的程式碼。

finalize是一個方法,屬於Object類的一個方法,而Object類是所有類的父類,Java 中允許使用 finalize()方法在垃圾收集器將物件從記憶體中清除出去之前做必要的清理工作。

6.

6. NoClassDefFoundError 和 ClassNotFoundException 區別?

java菜鳥到大佬——全網最全異常機制講解

6.7

. try-catch-finally 中,如果 catch 中 return 了,finally 還會執行嗎?

答:會執行,在 return 前執行。

「注意」:在 finally 中改變返回值的做法是不好的,因為如果存在 finally 程式碼塊,try中的 return 語句不會立馬返回呼叫者,而是記錄下返回值待 finally 程式碼塊執行完畢之後再向呼叫者返回其值,然後如果在 finally 中修改了返回值,就會返回修改後的值。顯然,在 finally 中返回或者修改返回值會對程式造成很大的困擾,C#中直接用編譯錯誤的方式來阻止程式設計師幹這種齷齪的事情,Java 中也可以透過提升編譯器的語法檢查級別來產生警告或錯誤。

6.8

. 常見的 RuntimeException 有哪些?

ClassCastException(類轉換異常)

IndexOutOfBoundsException(陣列越界)

NullPointerException(空指標)

ArrayStoreException(資料儲存異常,運算元組時型別不一致)

還有IO操作的BufferOverflowException異常

6.9

. Java常見異常有哪些

java。lang。IllegalAccessError:違法訪問錯誤。當一個應用試圖訪問、修改某個類的域(Field)或者呼叫其方法,但是又違反域或方法的可見性宣告,則丟擲該異常。

java。lang。Instantiation​Error:例項化錯誤。當一個應用試圖透過Java的new運算子構造一個抽象類或者介面時丟擲該異常。

java。lang。OutOfMemoryError:記憶體不足錯誤。當可用記憶體不足以讓Java虛擬機器分配給一個物件時丟擲該錯誤。

java。lang。StackOverflowError:堆疊溢位錯誤。當一個應用遞迴呼叫的層次太深而導致堆疊溢位或者陷入死迴圈時丟擲該錯誤。

java。lang。ClassCastException:類造型異常。假設有類A和B(A不是B的父類或子類),O是A的例項,那麼當強制將O構造為類B的例項時丟擲該異常。該異常經常被稱為強制型別轉換異常。

java。lang。ClassNotFoundException:找不到類異常。當應用試圖根據字串形式的類名構造類,而在遍歷CLASSPAH之後找不到對應名稱的class檔案時,丟擲該異常。

java。lang。ArithmeticException:算術條件異常。譬如:整數除零等。

java。lang。ArrayIndexOutOfBoundsException:陣列索引越界異常。當對陣列的索引值為負數或大於等於陣列大小時丟擲。

java。lang。IndexOutOfBoundsException:索引越界異常。當訪問某個序列的索引值小於0或大於等於序列大小時,丟擲該異常。

java。lang。InstantiationException:例項化異常。當試圖透過newInstance()方法建立某個類的例項,而該類是一個抽象類或介面時,丟擲該異常。

java。lang。NoSuchFieldException:屬性不存在異常。當訪問某個類的不存在的屬性時丟擲該異常。

java。lang。NoSuchMethodException:方法不存在異常。當訪問某個類的不存在的方法時丟擲該異常。

java。lang。NullPointerException:空指標異常。當應用試圖在要求使用物件的地方使用了null時,丟擲該異常。譬如:呼叫null物件的例項方法、訪問null物件的屬性、計算null物件的長度、使用throw語句丟擲null等等。

java。lang。NumberFormatException:數字格式異常。當試圖將一個String轉換為指定的數字型別,而該字串確不滿足數字型別要求的格式時,丟擲該異常。

java。lang。StringIndexOutOfBoundsException:字串索引越界異常。當使用索引值訪問某個字串中的字元,而該索引值小於0或大於等於序列大小時,丟擲該異常。

七.Java異常處理最佳實踐

在 Java 中處理異常並不是一個簡單的事情,但是大部分程式設計師又很容易忽略這個點。

其實處理異常不僅僅初學者很難理解,即使象我一樣工作了很多年的老碼農也需要花費很多時間來思考如何處理業務中的異常,而且在微服務架構中,你在系統的初始化階段就要想好這個事情,並設計出有規範的易於使用的可擴充套件的異常框架,這個往往會在團隊協作開發的時候起到很大的作用。

本文給出幾個被很多團隊使用的異常處理最佳實踐。

7.

1.

使用

finally 塊

或者

try-with-resource 語句

來進行資源的清理

我們經常會在程式碼中對檔案流進行處理,在處理完成之後需要關閉檔案流,當然我知道很多人會忘記。也有很多職場小白會把關閉程式碼寫在try{}塊中,比如下面這個程式碼:

java菜鳥到大佬——全網最全異常機制講解

問題來了,如果try程式碼塊中關閉流方法之前發生的異常,這時候直接進入到catch程式碼塊了,關閉流失效,這其實在真實工作中是一個非常嚴重的問題。

這個時候你應該把清理工作的程式碼放到 finally 裡去,或者使用 try-with-resource 特性。

7.

1.1 使用 finally 程式碼塊

與前面幾行 try 程式碼塊不同,finally 程式碼塊總是會被執行。不管 try 程式碼塊成功執行之後還是你在 catch 程式碼塊中處理完異常後都會執行。因此,你可以確保你清理了所有開啟的資源。

java菜鳥到大佬——全網最全異常機制講解

7.

1.2 Java 7 的 try-with-resource 語法

如果你的資源實現了 AutoCloseable 介面,你可以使用這個語法。大多數的 Java 標準資源都繼承了這個介面。當你在 try 子句中開啟資源,資源會在 try 程式碼塊執行後或異常處理後自動關閉。

java菜鳥到大佬——全網最全異常機制講解

7.2 儘量使用標準的異常

程式碼重用是值得提倡的,這是一條通用規則,異常也不例外。

重用現有的異常有幾個好處:

它使得你的API更加易於學習和使用,因為它與程式設計師原來已經熟悉的習慣用法是一致的。

對於用到這些API的程式而言,它們的可讀性更好,因為它們不會充斥著程式設計師不熟悉的異常。

異常類越少,意味著記憶體佔用越小,並且轉載這些類的時間開銷也越小。

Java標準異常中有幾個是經常被使用的異常。如下表格:

java菜鳥到大佬——全網最全異常機制講解

雖然它們是Java平臺庫迄今為止最常被重用的異常,但是,在許可的條件下,其它的異常也可以被重用。例如,如果你要實現諸如複數或者矩陣之類的算術物件,那麼重用ArithmeticException和NumberFormatException將是非常合適的。如果一個異常滿足你的需要,則不要猶豫,使用就可以,不過你一定要確保丟擲異常的條件與該異常的文件中描述的條件一致。這種重用必須建立在語義的基礎上,而不是名字的基礎上。

最後,一定要清楚,選擇重用哪一種異常並沒有必須遵循的規則。例如,考慮紙牌物件的情形,假設有一個用於發牌操作的方法,它的引數(handSize)是發一手牌的紙牌張數。假設呼叫者在這個引數中傳遞的值大於整副牌的剩餘張數。那麼這種情形既可以被解釋為IllegalArgumentException(handSize的值太大),也可以被解釋為IllegalStateException(相對客戶的請求而言,紙牌物件的紙牌太少)。

7.3

異常文件說明

必需要有

在文件中對異常進行詳細的說明,包括異常的code,message,以及出現的場景,可能引發的問題。

public void doSomething(String input) throws MyBusinessException {

。。。

}

7.4

不要捕獲 Throwable 類

下面的反例值得正視:

java菜鳥到大佬——全網最全異常機制講解

7.5

. 不要

異常

忽略掉

在實際開發過程中,有些時候,catch了異常,但是呢又不知道如何處理,甚至沒有記錄任何日誌。一旦異常發生,我們無法獲取到任何錯誤資訊,阻礙我們排查問題。

java菜鳥到大佬——全網最全異常機制講解

合理的做法是至少要記錄異常的資訊。

java菜鳥到大佬——全網最全異常機制講解

7.6

. 不要使用異常控制程式的流程

不應該使用異常控制應用的執行流程,例如,本應該使用if語句進行條件判斷的情況下,你卻使用異常處理,這是非常不好的習慣,會嚴重影響應用的效能。

7.7

. 使用標準異常

如果使用內建的異常可以解決問題,就不要定義自己的異常。

Java

API

提供了上百種針對不同情況的異常型別,在開發中首先儘可能使用 Java API 提供的異常,如果標準的異常不能滿足你的要求,這時候建立自己的定製異常。儘可能得使用標準異常有利於新加入的開發者看懂專案程式碼。

7.8.

異常會影響效能

實驗

首先,我們看看沒有try-catch情況下,進行100萬次加法的耗時:

java菜鳥到大佬——全網最全異常機制講解

經過5次統計,其平均耗時為:1816048納秒,即1。8毫秒。

接著,我們來看看在有try-catch情況下,進行100萬次加法的耗時:

java菜鳥到大佬——全網最全異常機制講解

經過5次統計,其平均耗時為:1928394納秒,即1。9毫秒。

我們再來看看,如果try-catch丟擲異常,進行100萬次加法的耗時:

java菜鳥到大佬——全網最全異常機制講解

經過5次統計,其平均耗時為:780950471納秒,即780毫秒。

經過上面三次統計,我們可以看到在沒有

try catch時,耗時1。8毫秒。在有try catch 但是沒有丟擲異常,耗時1。9毫秒。在有丟擲異常,耗時780毫秒。我們能得出一個結論:

如果try catch沒有丟擲異常,那麼其對效能幾乎沒有影響。但如果丟擲異常,那對程式將造成幾百倍的效能影響。

異常處理的效能成本非常高,每個

Java 程式設計師在開發時都應牢記這句話。建立一個異常非常慢,丟擲一個異常又會消耗1~5ms,當一個異常在應用的多個層級之間傳遞時,會拖累整個應用的效能。

儘管使用異常有利於

Java 開發,但是在應用中最好不要捕獲太多的呼叫棧,因為在很多情況下都不需要列印呼叫棧就知道哪裡出錯了。因此,異常訊息應該提供恰到好處的資訊。

Top