欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java異常處理中的各種細節(jié)匯總

 更新時間:2019年01月11日 10:42:16   作者:承香墨影  
這篇文章主要給大家介紹了關于Java異常處理中的各種細節(jié)的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧

前言

今天我們來討論一下,程序中的錯誤處理。

在任何一個穩(wěn)定的程序中,都會有大量的代碼在處理錯誤,有一些業(yè)務錯誤,我們可以通過主動檢查判斷來規(guī)避,可對于一些不能主動判斷的錯誤,例如 RuntimeException,我們就需要使用 try-catch-finally 語句了。

有人說,錯誤處理并不難啊,try-catch-finally 一把梭,try 放功能代碼,在 catch 中捕獲異常、處理異常,finally 中寫那些無論是否發(fā)生異常,都要執(zhí)行的代碼,這很簡單啊。

處理錯誤的代碼,確實并不難寫,可是想把錯誤處理寫好,也并不是一件容易的事情。

接下來我們就從實現(xiàn)到 JVM 原理,講清楚 Java 的異常處理。

學東西,我還是推薦要帶著問題去探索,提前思考幾個問題吧:

  • 一個方法,異常捕獲塊中,不同的地方的 return 語句,誰會生效?
  • catch 和 finally 中出現(xiàn)異常,會如何處理?
  • try-catch 是否影響效率?
  • Java 異常捕獲的原理?

二、Java 異常處理

2.1 概述

既然是異常處理,肯定是區(qū)分異常發(fā)生和捕獲、處理異常,這也正是組成異常處理的兩大要素。

在 Java 中,拋出的異??梢苑譃轱@示異常和隱式異常,這種區(qū)分主要來自拋出異常的主體是什么,顯示和隱式也是站在應用程序的視角來區(qū)分的。

顯示異常的主體是當前我們的應用程序,它指的是在應用程序中使用 “throw” 關鍵字,主動將異常實例拋出。而隱式異常就不受我們控制, 它觸發(fā)的主體是 Java 虛擬機,指的是 Java 虛擬機在執(zhí)行過程中,遇到了無法繼續(xù)執(zhí)行的異常狀態(tài),續(xù)而將異常拋出。

對于隱式異常,在觸發(fā)時,需要顯示捕獲(try-catch),或者在方法頭上,用 "throw" 關鍵字聲明,交由調(diào)用者捕獲處理。

2.2 使用異常捕獲

在我們編寫異常處理代碼的時候,主要就是使用前面介紹到的 try-catch-finally 這三種代碼塊。


  • try 代碼塊:包含待監(jiān)控異常的代碼。
  • catch 代碼塊:緊跟 try 塊之后,可以指定異常類型。允許指定捕獲多種不同的異常,catch 塊用來捕獲在 try 塊中出發(fā)的某個指定類型的異常。
  • finally 代碼塊:緊跟 try 塊或 catch 塊之后,用來聲明一段必定會運行的代碼。例如用來清理一些資源。

catch 允許存在多個,用于針對不同的異常做不同的處理。如果使用 catch 捕獲多種異常,各個 catch 塊是互斥的,和 switch 語句類似,優(yōu)先級是從上到下,只能選擇其一去處理異常。

既然 try-catch-finally 存在多種情況,并且在發(fā)生異常和不發(fā)生異常時,表現(xiàn)是不一致的,我們就分清楚來單獨分析。

1. try塊中,未發(fā)生異常

不觸發(fā)異常,當然是我們樂于看見的。在這種情況下,如果有 finally 塊,它會在 try 塊之后運行,catch 塊永遠也不會被運行。

2. try塊中,發(fā)生異常

在發(fā)生異常時,會首先檢查異常類型,是否存在于我們的 catch 塊中指定的待捕獲異常。如果存在,則這個異常被捕獲,對應的 catch 塊代碼則開始運行,finally 塊代碼緊隨其后。

例如:我們只監(jiān)聽了空指針(NullPointerException),此時如果發(fā)生了除數(shù)為 0 的崩潰(ArithmeticException),則是不會被處理的。

當觸發(fā)了我們未捕獲的異常時,finally 代碼依然會被執(zhí)行,在執(zhí)行完畢后,繼續(xù)將異?!皰伋鋈ァ薄?br />

3. catch 或者 finally 發(fā)生異常

catch 代碼塊和 finally 代碼塊,也是我們編寫的,理論上也是有出錯的可能。

那么這兩段代碼發(fā)生異常,會出現(xiàn)什么情況呢?

當在 catch 代碼塊中發(fā)生異常時,此時的表現(xiàn)取決于 finally 代碼塊中是否存在 return 語句。如果存在,則 finally 代碼塊的代碼執(zhí)行完畢直接返回,否則會在 finally 代碼塊執(zhí)行完畢后,將 catch 代碼中新產(chǎn)生的異常,向外拋出去。

而在極端情況下,finally 代碼塊發(fā)生了異常,則此時會中斷 finally 代碼塊的執(zhí)行,直接將異常向外拋出。

2.3 異常捕獲的返回值

再回頭看看第一個問題,假如我們寫了一個方法,其中的代碼被 try-catch-finally 包裹住進行異常處理,此時如果我們在多個地方都有 return 語句,最終誰的會被執(zhí)行?

如上圖所示,在完整的 try-catch-finally 語句中,finally 都是最后執(zhí)行的,假設 finally 代碼塊中存在 return 語句,則直接返回,它是優(yōu)先級最高的。

一般我們不建議在 finally 代碼塊中添加 return 語句,因為這會破壞并阻止異常的拋出,導致不宜排查的崩潰。

2.4 異常的類型

在 Java 中,所有的異常,其實都是一個個異常類,它們都是 Throwable 類或其子類的實例。

Throwable 有兩大子類,Exception 和 Error。

  • Exception:表示程序可能需要捕獲并且處理的異常。
  • Error:表示當觸發(fā) Error 時,它的執(zhí)行狀態(tài)已經(jīng)無法恢復了,需要中止線程甚至是中止虛擬機。這是不應該被我們應用程序所捕獲的異常。

通常,我們只需要捕獲 Exception 就可以了。但 Exception 中,有一個特殊的子類 RuntimeException,即運行時錯誤,它是在程序運行時,動態(tài)出現(xiàn)的一些異常。比較常見的就是 NullPointerException、ArrayIndexOutOfBoundsException 等。
Error 和 RuntimeException 都屬于非檢查異常(Unchecked Exception),與之相對的就是普通 Exception 這種屬于檢查異常(Checked Exception)。

所有檢查異常都需要在程序中,用代碼顯式捕獲,或者在方法中用 throw 關鍵字顯式標注。其實意思很明顯,要不你自己處理了,要不你拋出去讓別人處理。

這種檢查異常的機制,是在編譯期間進行檢查的,所以如果不按此規(guī)范處理,在編譯器編譯代碼時,就會拋出異常。

2.5 異常處理的性能問題

對于異常處理的性能問題,其實是一個很有爭議的問題,有人覺得異常處理是多做了一些工作,肯定對性能是有影響的。但是也有人覺得異常處理的影響,和增加一個 if-else 屬于同種量級,對性能的影響其實微乎其微,是在可以接受的范圍內(nèi)的。

既然有爭議,最簡單的辦法是寫個 Demo 驗證一下。當然,我們這里是需要區(qū)分不同的情況,然后根據(jù)解決對比的。
一個最簡單的 for 循環(huán) 100w 次,在其中做一個 a++ 的自增操作。

  • A:無任何 try-catch 語句。
  • B:將 a++ 包在 try 代碼塊中。
  • C:在 try 代碼塊中,觸發(fā)一個異常。

就是一個簡單的 for 循環(huán),就不貼代碼了,異常通過 5/0 這樣的運算,觸發(fā)除數(shù)為 0 的 ArithmeticException 異常,并在 JDK 1.8 的環(huán)境下運行。

為了避免影響采樣結果,每個例子都單獨運行 10 遍之后,取平均值(單位納秒)。

到這里基本上就可以得出結論了,在沒有發(fā)生異常的情況下,try-catch 對性能的影響微乎其微。但是一旦發(fā)生異常,性能上則是災難性的。

因此,我們應該盡可能的避免通過異常來處理正常的邏輯檢查,這樣可以確保不會因為發(fā)生異常而導致性能問題。

至于為什么發(fā)生異常時,性能差別會有如此之大,就需要從 Java 虛擬機 JVM 的角度來分析了,后面會詳細分析。

2.6 異常處理無法覆蓋異步回調(diào)

try-catch-finally 確實很好用,但是它并不能捕獲,異步回調(diào)中的異常。try 語句里的方法,如果允許在另外一個線程中,其中拋出的異常,是無法在調(diào)用者這個線程中捕獲的。

這一點在使用的過程中,需要特別注意。

三、JVM 如何處理異常

3.1 JVM 異常處理概述

接下來我們從 JVM 的角度,分析 JVM 如何處理異常。

當異常發(fā)生時,異常實例的構建,是非常消耗性能的。這是由于在構造異常實例時,Java 虛擬機需要生成該異常的異常棧(stack trace)。

異常棧會逐一訪問當前線程的 Java 棧幀,以及各種調(diào)試信息。包括棧幀所指向的方法名,方法所在的類名、文件名以及在代碼中是第幾行觸發(fā)的異常。

這些異常輸出到 Log 中,就是我們熟悉的崩潰日志(崩潰棧)。

3.2 崩潰實例分析異常處理

當把 Java 代碼編譯成字節(jié)碼后,每個方法都會附帶一個異常表,其中記錄了當前方法的異常處理。

下面直接舉個例子,寫一個最簡單的 try-catch 類。

使用 javap -c 進行反編譯成字節(jié)碼。

可以看到,末尾的 Exceptions Table 就是異常表。異常表中的每一條記錄,都代表了一個異常處理器。

異常處理器中,標記了當前異常監(jiān)控的起始、結束代碼索引,和異常處理器的索引。其中 from 指針和 to 指針標識了該異常處理器所監(jiān)控的代碼范圍,target 指針則指向異常處理器的起始位置,type 則為最后監(jiān)聽的異常。

例如上面的例子中,main 函數(shù)中存在異常表,Exception 的異常監(jiān)聽代碼范圍分別是 [0,8)(不包括 8),異常處理器的索引為 11。

繼續(xù)分析異常處理流程,還需要區(qū)分是否命中異常。

1. 命中異常

當程序發(fā)生異常時,Java 虛擬機會從上到下遍歷異常表中所有的記錄。當發(fā)現(xiàn)觸發(fā)異常的字節(jié)碼的索引值,在某個異常表中某個異常監(jiān)控的范圍內(nèi)。Java 虛擬機會判斷所拋出的異常和該條異常監(jiān)聽的異常類型,是否匹配。如果能匹配上,Java 虛擬機會將控制流轉向至該此異常處理器的 target 索引指向的字節(jié)碼,這是命中異常的情況。

2. 未命中異常

而如果遍歷完異常表中所有的異常處理器之后,仍未匹配到異常處理器,那么它會彈出當前方法對應的 Java 棧幀?;氐剿恼{(diào)用者,在其中重復此過程。

最壞的情況下,Java 虛擬機需要遍歷當前線程 Java 棧上所有方法的異常表。

3.3 編譯后的 finally 代碼塊

我們寫的代碼,其實終歸是給人讀的,但是編譯器干的事兒,都不是人事兒。它會把代碼做一些特殊的處理,只是為了讓自己更好解析和執(zhí)行。

編譯器對 finally 代碼塊,就是這樣處理的。在當前版本的 Java 編譯器中,會將 finally 代碼塊的內(nèi)容,復制幾份,分別放在所有可能執(zhí)行的代碼路徑的出口中。

寫個 Demo 驗證一下,代碼如下。

繼續(xù) javap -c 反編譯成字節(jié)碼。

這個例子中,為了更清晰的看到 finally 代碼塊,我在其中輸出的一段 Log “run finally”。可以看到,編譯結果中,包含了三份 finally 代碼塊。

其中,前兩份分別位于 try 代碼塊和 catch 代碼塊的正常執(zhí)行路徑出口。最后一份則作為全局的異常處理器,監(jiān)控 try 代碼塊以及 catch 代碼塊。它將捕獲 try 代碼塊觸發(fā)并且未命中 catch 代碼塊捕獲的異常,以及在 catch 代碼塊觸發(fā)的異常。
而 finally 的代碼,如果出現(xiàn)異常,就不是當前方法所能處理的了,會直接向外拋出。

3.4 異常表中的 any 是什么?

從上圖中可以看到,在異常表中,還存在兩個 any 的信息。

第一個信息的 from 和 to 的范圍就是 try 代碼塊,等于是對 catch 遺漏異常的一種補充,表示會處理所有種類的異常。

第二個信息的 from 和 to 的范圍,仔細看能看到它其實是 catch 代碼塊,這也正好印證了我們上面的結論,catch 代碼塊其實也被異常處理器監(jiān)控著。

只是如果命中了 any 之后,因為沒有對應的異常處理器,會繼續(xù)向上拋出去,交由該方法的調(diào)用方法處理。

四、總結

到這里我們就基本上講清楚了 Java 異常處理的所有內(nèi)容。

在日常開發(fā)當中,應該盡量避免使用異常處理的機制來處理業(yè)務邏輯,例如很多代碼中,類型轉換就使用 try-catch 來處理,其實是很不可取的。

異常捕獲對應用程序的性能確實有影響,但也是分情況的。

一旦異常被拋出來,方法也就跟著 return 了,捕獲異常棧時會導致性能變得很慢,尤其是調(diào)用棧比較深的時候。

但是從另一個角度來說,異常拋出時,基本上表明程序的錯誤。應用程序在大多數(shù)情況下,應該是在沒有異常情況的環(huán)境下運行的。所以,異常情況應該是少數(shù)情況,只要我們不濫用異常處理,基本上不會影響正常處理的性能問題。

好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關文章

  • SpringBoot項目的多文件兼多線程上傳下載

    SpringBoot項目的多文件兼多線程上傳下載

    本文主要介紹了SpringBoot項目的多文件兼多線程上傳下載,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-04-04
  • 一起來看看springboot集成redis的使用注解

    一起來看看springboot集成redis的使用注解

    這篇文章主要為大家詳細介紹了springboot集成redis的使用注解,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • SpringBoot處理大量請求數(shù)據(jù)的傳輸問題的方法小結

    SpringBoot處理大量請求數(shù)據(jù)的傳輸問題的方法小結

    在Spring?Boot項目常常需要中處理大量請求數(shù)據(jù)的傳輸問題,這篇文章主要為大家整理了一些常用的方法,感興趣的小伙伴可以跟隨小編一起學習一下
    2024-01-01
  • java發(fā)送get請求和post請求示例

    java發(fā)送get請求和post請求示例

    這篇文章主要介紹了java發(fā)送get請求和post請求示例,需要的朋友可以參考下
    2014-03-03
  • SpringBoot如何集成Kafka低版本和高版本

    SpringBoot如何集成Kafka低版本和高版本

    這篇文章主要介紹了SpringBoot如何集成Kafka低版本和高版本問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • Java?Stream?API?使代碼更出色的操作完全攻略

    Java?Stream?API?使代碼更出色的操作完全攻略

    這篇文章主要介紹了Java?Stream?API?使代碼更出色的操作完全攻略,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • ES模糊查詢失效的坑以及解決方案

    ES模糊查詢失效的坑以及解決方案

    ES的查詢原理是按分詞建立索引,根據(jù)要保存的內(nèi)容先分詞,然后按照分詞的結果建立索引,這篇文章主要給大家介紹了關于ES模糊查詢失效的坑及解決方案的相關資料,需要的朋友可以參考下
    2023-09-09
  • java實現(xiàn)CSV文件導入與導出功能

    java實現(xiàn)CSV文件導入與導出功能

    這篇文章主要為大家詳細介紹了java實現(xiàn)CSV文件導入與導出,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • SpringBoot之spring.factories的使用方式

    SpringBoot之spring.factories的使用方式

    這篇文章主要介紹了SpringBoot之spring.factories的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • Java實現(xiàn)對視頻進行截圖的方法【附ffmpeg下載】

    Java實現(xiàn)對視頻進行截圖的方法【附ffmpeg下載】

    這篇文章主要介紹了Java實現(xiàn)對視頻進行截圖的方法,結合實例形式分析了Java使用ffmpeg針對視頻進行截圖的相關操作技巧,并附帶ffmpeg.exe文件供讀者下載使用,需要的朋友可以參考下
    2018-01-01

最新評論