Java基礎之異常處理機制

2020-12-14 老夫編程說
Java基礎之異常處理機制

什麼是異常

從事Java開發的小夥伴對於「異常」應該不陌生,因為每天都會遇到不少異常,或捕獲,或拋出。那究竟什麼是異常?異常即非正常的,不同於平常、一般化的情況。在平時生活中,醫生會說你身體的某個部位有異常,該異常會有什麼什麼的影響,是由某某原因引起的;我每天都準時打卡,按時上下班,那麼我本月的考勤是正常的,反之,但凡有遲到、曠工、早退的情況之一的,我本月的考情就會有異常

而在程序中,代碼在運行中如果出現運行錯誤,程序會終止運行,這時由於錯誤導致程序運行終止的情況就是程序出現了異常。異常並不是指語法錯誤,因為如果語法錯了,編譯就通不過,不會產生JVM能夠識別的字節碼文件,是沒法運行起來的,所以只有運行中的程序才會有異常一說。

Java 異常體系

異常處理是衡量一門語言是否成熟的標準之一,C系列的語言諸如:Java、C++、C 等都支持異常處理,有自己的一套異常處理機制。異常處理機制可以讓程序有更好的容錯性,使代碼更加健壯;但C語言卻沒有異常處理機制,C程式設計師一般都是利用方法的返回值來實現異常處理,使用 if + 條件 來判斷正常和異常情況,使用特定返回值來表示異常情況。

引入異常處理機制可以解決的問題有:

使用方法的返回值來表示異常情況有局限性,需要無窮列舉所有的異常情況;異常流程代碼和正常流程代碼混合一起,代碼往往很臃腫,也很複雜性;代碼的可讀性和可維護性都不高;隨著系統規模的不斷擴大,代碼很難維護,特別是系統可拓展性差;通過一下這個案例來說明引入異常之前的處理方式

// 汽車class Car {// 汽車是否正常運行 public static final boolean IS_OK = true; // 汽車運行 public boolean run(int wheelNum) { if (wheelNum == 4) { return true; } return false; }}// 上班族class Officer { private Car car; public Officer(Car car) { this.car = car; } // 開車去上班 public boolean goWorkByCar(int wheelNum) { if (car == null) { // 只能選擇其他出行方式了 goWorkByOther(); return false; } boolean result = car.run(wheelNum); if (result) { System.out.println("升職,加薪,迎娶白富美"); return true; } System.out.println("遲到,扣薪,領導天天噴"); return false; } // 其他方式上班 public void goWorkByOther() { System.out.println("其他方式上班"); }}// 運行demopublic class WorkDemo { public static void main(String[] args) { Car car = new Car(); Officer officer = new Officer(car); officer.goWorkByCar(4); }}

上述案例中,只是簡單粗暴地把汽車的狀態分為true和false兩種,細化不夠,不能體現出汽車的詳細狀態;業務邏輯也很局限,如果要拓展,就要花費更大的成本。

針對於上述的問題,Java基於面向對象的思想提出了解決方案:

把不同類型的異常情況使用不同的類來表示,不同的異常類有共同的父類;分離異常流程代碼和正確流程代碼;規範了異常處理機制,靈活處理異常,能處理就將其捕獲並處理,如果處理不了異常,就將其交給調用者來處理;Java 異常體系:Java API文檔中的詳細介紹如下

Java 異常體系

Error:表示錯誤,一般指JVM相關的不可修復的錯誤,如:系統崩潰、內存溢出、JVM內部錯誤等,由JVM拋出,我們一般情況下不需要處理,幾乎其所有的子類都是以「Error」作為類名後綴;比如:StackOverflowError,當應用程式遞歸太深而發生內存溢出時,就會拋出該錯誤。Exception:表示異常,指程序中出現不正常的情況,異常一般都是需要程式設計師來處理的(可以捕獲或者拋出);幾乎其所有的子類都是以「Exception」作為類名的後綴;Throwable:在Java 體系中,Throwable類是所有錯誤異常的父類;當出現了沒見過的異常時,可以將異常類的類名拿到Java API文檔中去查找,通過文章介紹即可獲得異常的詳細信息,以及其在Java中的繼承、實現體系;常見的Exception有:

NullPointerException:空指針異常,一般當對象為null的時候,對該對象做操作時會出現該異常;ArrayIndexOutOfBoundsException:數組的索引越界,操作數組時使用的索引超出了數組的數據範圍會出現;NumberFormatException:數字格式化異常,把非數字的數據類型轉換為數字類型時使用了非法的轉換對象;Java 的異常詳解:

ExceptionDemo.java

運行結果如下:

Java 的異常詳解

如果出現異常,會立刻中斷運行中的程序,所以必須處理異常,而處理方式有兩種:

throws:當前方法不處理,而是聲明拋出,由該方法的調用者來處理;try-catch:在當前方法中使用try-catch的語句塊來處理異常;

捕獲異常 try-catch

出現異常之後,程序會中斷執行,所以異常必須處理;處理的方式有兩種:捕獲拋出,兩種方式二選一即可。我們先來運行一個案例來證明:

沒有異常的案例

運行結果如下:

沒有異常的案例 運行結果

通過查看運行結果,是我們期望的運行結果,代碼運行成功;那麼接下來我們對上述案例稍作修改,再來看其運行結果如何:

有異常的案例

運行結果如下:

有異常的案例 運行結果

通過查看運行結果,運行結果並不是我們想要的,代碼中出現了異常,代碼被中斷運行。對比兩次的運行結果,我們可以得出結論:出現異常之後,程序會中斷執行,異常必須處理。

try-catch 異常捕獲:使用try-catch捕獲單個異常,語法如下:

try-catch 異常捕獲語法

注意:try和catch都不能單獨使用,必須連用。

所以可以對上述出現異常的代碼使用try-catch來處理:

try-catch 案例

運行結果如下:

try-catch 案例 運行結果

通過查看運行結果,不難發現,使用try-catch之後,程序遇到異常時不再中斷執行,而是跳過異常代碼及其之後的在try-catch中的剩餘代碼語句,來到catch代碼塊中執行我們定義的異常處理代碼;

在上述案例中是列印出了異常信息,異常對象信息,異常棧追蹤;其中的3個方法都是Throwable類的方法:

String getMessage():獲取異常的詳細描述信息;String toString():獲取異常的類型、異常描述信息;void printStackTrace():列印異常的跟蹤棧信息並輸出到控制臺,但不能在System.out.println()中使用該方法;其中包含了異常的類型、異常的原因、異常出現的位置;在開發和調試階段,該方法都很有用,方便調試和修改;

try-catch 的底層執行分析

使用try-catch捕獲多個異常:語法如下:

try-catch捕獲多個異常 語法

注意

一個catch語句,只能捕獲一種類型的異常,如果需要捕獲多種異常類型,就得使用多個catch語句;try-catch中的代碼在只會出現一種類型的異常,只能一個catch捕獲,不可能同時匹配多個catch;在有多個catch語句的代碼中出現異常時,會從上到下依次匹配catch語句,所以多個catch語句應該按照從子類到父類的順序依次定義;一旦匹配上其中一個catch之後,便不會匹配剩餘的catch,而是會跳出try-catch,執行之後的代碼;捕獲多個異常的案例:

捕獲多個異常的案例

運行結果如下:

捕獲多個異常的案例

拋出異常

拋出異常有兩種方式:

throw:用於方法內部,用於給調用者返回一個異常對象,和return一樣會結束當前方法;throws:運用於方法聲明之上,定義於方法參數之後,表示當前方法不處理異常,而是提醒該方法的調用者來處理拋出的異常(一個或者多個異常);如:private static int divide(int num1, int num2) throws Exception {}throw語句:運用於方法內部,拋出一個具體的異常對象,中止方法的執行,其語法格式如下:

throw new 異常類("異常信息");

一般的,當一個方法出現異常的情況,我們不知道該方法應該返回什麼時,此時就可以返回一個錯誤,在catch語句塊中使用throw繼續向上拋出異常。return 是返回一個值,throw 是返回一個錯誤,返回給該方法的調用者。比如:String類的charAt方法就是一個很好的例子:

String類的charAt方法

throws 語句:

如果每一個方法都放棄處理異常都直接通過throws聲明拋出,最後異常會拋到main方法,如果此時main方法還不處理,會繼續拋出給JVM,JVM底層的處理機制就是列印異常的跟蹤棧信息;runtime異常,默認就是這種處理方式。

方法重寫(Override)中的throws:

一同:方法的籤名必須相同。

兩小:

子類方法返回類型和父類方法返回類型相同,或是其子類;子類方法不能聲明拋出新的異常一大:子類方法的訪問權限必須大於等於父類方法的訪問權限。

異常分類

Throwable體系

異常(Exception)根據其在編譯時期還是運行時期去檢查異常可分為:checked異常runtime異常,

runtime異常:又稱運行時期異常,此類型的異常在運行時期檢查;在編譯時期,運行異常並不會檢測,就不會出現,只有在運行到相關代碼時才會出現;RuntimeException自身及其子類異常都屬於runtime異常checked異常:又稱編譯時期異常,此類型的異常在編譯時期就會檢查,而且是必須處理的,如果沒有處理,就會導致編譯失敗;除了runtime異常之外的其他異常(包括Exception自身)都屬於checked異常

jdk 文檔中的異常體系
向下看
異常分類

自定義異常

Java中有著不同的定義好的異常類,分別表示著某一種具體的異常情況,在開發中總是有些異常情況是Java SE庫中沒有定義好的,此時就可以根據自己業務的異常情況來定義異常類;我們把這樣的異常類稱為自定義異常類。

自定義異常類的方式:

受檢查的異常:自定義一個受檢查的異常類需要繼承於java.lang.Exception;運行時異常:自定義一個運行時期檢查的異常類,需要繼承於java.lang.RuntimeException;一般在開發中,自定義的異常都是運行時異常。

解決開車上班的案例

使用自定義異常解決開車上班案例

異常轉譯和異常鏈

異常轉譯:位於最外層的業務系統不需要關心底層的異常細節,我們通過捕獲原始的異常將其轉換為一個新的不同類型的異常然後再向上拋出;這個過程稱為異常轉譯

在上述例子中:我的車壞了,在catch中重新拋出一個新的異常(OfficerException)給我的調用者(老闆),不能把車的異常信息拋給老闆看,因為老闆不關心這些細節,關心的我是否遲到。

異常鏈:把原始異常包裝為新的異常類,形成多個異常的有序排列;異常鏈由於更加清楚、準確的定位異常出現的位置;在下述案例中,異常一層層拋出,直至異常被處理,在這個過程中,異常鏈就產生了:

Java7的異常新特性

1.增強的throw :對比Java 6 和 Java 7 中對於拋出異常的改進來體現

2.多異常捕獲:重寫捕獲多個異常案例來體現

多異常捕獲

3.自動資源關閉:資源類必須直接或者間接實現java.lang.AutoCloseable接口

資源自動關閉

finally代碼塊

finally語句塊表示無論如何(也包括發生異常時)都會最終執行的代碼塊,比如:當我們在try語句塊中打開了一些物理資源(磁碟文件/網絡連接/資料庫連接等),在使用完之後,都得最終關閉打開的資源。

finally的兩種用法:

1. try...finally:此時沒有catch來捕獲異常,因為此時根據應用場景會拋出異常,我們程式設計師自己不處理;

try...finally 的使用

2. try...catch....finally:程式設計師自己需要處理異常,最終得手動關閉資源。

需要注意的是:finally不能單獨使用

try...catch...finally 的使用

finally不執行的情況:

當只有在try或者catch中調用退出JVM的相關方法此時finally才不會執行,否則finally修飾的代碼塊永遠會執行。比如:System.exit(0);//退出JVM

finally 和 return

如果finally和return語句同時存在,永遠返回finally中的結果,但是應該避免這種情況的出現。詳情看如下的案例:

如果finally和return語句同時存在,永

還有另一種情況也很有趣,一起來看看:

很有趣的finally例子

為什麼會出現這種情況呢?首先finally肯定是會被執行的,所以a++之後a的值變成了14,但是finally中沒有返回值,值為14的變量a並沒有被返回;然後接著執行return a;這裡的a的值在方法執行之初就已經確定了,故返回的值是13。

處理異常的原則:

異常只能用於非正常情況,try-catch的存在也會影響性能,儘量縮小try-catch的代碼範圍;需要為異常提供說明文檔,可以參考Java doc,如果自定義了異常或某一個方法拋出了異常,應該在文檔注釋中詳細說明;儘可能避免異常的出現,如NullPointerException等;異常的粒度很重要,應該為一個基本操作定義一個 try-catch 塊,切忌將幾百行代碼放到一個 try-catch 塊中;不建議在循環中進行異常處理,應該在循環外對異常進行捕獲處理(在循環之外使用try-catch);自定義異常儘量使用RuntimeException類型的,並且要儘量避開已存在的異常;

小結

1. 五個關鍵字:try、catch、finally、throw、throws

2. 異常體系的兩個繼承結構:

Throwable類有兩個子類:Error和Exception。Exception類有一個特殊的子類:RuntimeException。RuntimeException類及其子類稱之為runtime異常

Exception類和子類中除了RuntimeException體系的其他類稱之為checked異常

3. 自定義異常類

4. Error和Exception的區別和關係

5. checked異常和runtime異常的區別

6. finally關鍵字及其相關知識

7. finally和return的執行順序

8. throw和throws的區別

完結。

老夫雖然不正經,但老夫一身的才華!

相關焦點

  • 處理Java異常的9個最佳實踐
    本文由程式設計師新視界原創翻譯英文原文連結:https://dzone.com/articles/9-best-practices-to-handle-exceptions-in-java說起Java異常處理,心情就變得沉重起來。
  • java中的try是什麼?|java的異常處理類型
    1、try-是java異常處理類型中的一個。那麼,異常處理又是啥?2、異常-說的就是編寫程序時出現的錯誤。2.1:java提供異常處理類Throwable2.2:Throwable類分為Error和Exception兩個子類2.3:Error由java本身來處理2.4:我們要捕獲和拋出的異常是Exception這類的異常
  • Java中最正確的異常處理方式
    比如說,你的代碼少了一個分號,那麼運行出來結果是提示是錯誤 java.lang.Error ;如果你用 System.out.println(11/0) ,那麼你是因為你用0做了除數,會拋出 java.lang.ArithmeticException 的異常。
  • JAVA 異常處理小技巧!你get到了嗎?
    1、異常:就是程序運行時出現不正常情況異常由來:問題也是現實生活中一個具體的事物,也可以通過java的類的形式進行描述。並封裝成對象。其實就是java對不正常情況進行描述後的對象體現。對於問題的劃分(兩種):一種是嚴重的問題,一種是非嚴重的問題;對於嚴重的,java通過Error類進行描述。對於Error一般不編寫針對性的代碼對其進行編寫對於非嚴重的,java通過Exception類進行描述。
  • Java基礎面試題簡單總結
    java編譯器要求方法必須聲明拋出可能發生的非運行時異常,但是並不要求必須聲明拋出未被捕獲的運行時異常。43、Java中的異常處理機制的簡單原理和應用答:當JAVA程序違反了JAVA的語義規則時,JAVA虛擬機就會將發生的錯誤表示為一個異常。違反語義規則包括2種情況。一種是JAVA類庫內置的語義檢查。
  • 如何優雅的設計 Java 異常
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫導語異常處理是程序開發中必不可少操作之一,但如何正確優雅的對異常進行處理確是一門學問
  • Java異常的十大問題總結
    本文總結了有關Java異常的十大常見問題。1.已檢查與未檢查簡而言之,必須在方法中顯式捕獲已檢查的異常或在方法的throws子句中聲明該異常。未檢查的異常是由無法解決的問題引起的,例如被零除,空指針等。檢查的異常特別重要,因為您希望使用API的其他開發人員知道如何處理異常。2.異常管理的最佳實踐如果可以正確處理異常,則應將其捕獲,否則應將其引發。
  • 如何處理Java中出現的異常?這九種方式值得一試!
    【IT168 評論】處理 Java 中的異常情況並不是個輕鬆的話題,什麼樣的異常需要如何處理,常讓新手們覺得難以理解,甚至有經驗的開發人員也可能花幾個小時討論不決。  正因如此,大多數開發團隊有著一套自己的規則,如果你是團隊裡的新人,你會對團隊之間對於這個規則的巨大差別感到驚訝。
  • Java異常出現如何處理
    我們在編寫運行過程中常見的可避免不可避免的異常報錯該怎麼處理?今天就具體告訴大家如何處理Java異常。Java的異常處理本質上時拋出異常和捕獲異常。拋出異常指的是如果程序中出現了異常,沒有辦法將具體的異常列印出來,不做任何處理。
  • C Sharp 基礎知識系列 - 15 異常處理
    那麼為什麼需要異常處理機制呢?這是因為我們需要我們的程序不能是一個精美的易碎品,所以必須有一定程度的容錯性,或者叫強壯性。這時候就要求程式設計師在開發過程中,對一些可能出現的場景進行預估,然後預先處理這些錯誤。而異常處理機制使得程式設計師更加簡單方便的處理這些錯誤。1.
  • Java簡潔之道:別讓空值處理那麼「難堪」
    在編程中,最經常遇到的異常是空指針異常(java.lang.NullPointerException),根據某個業務場景,我們就會編寫如下的代碼,並使用if……else.判斷對象是否為空。傳統的空值處理邏輯可以明顯地看到,無休止的if判斷讓程序變得臃腫、冗長。這個問題怎麼解決呢,來看看Optional類是如何處理的,如下。
  • 開發java過程中如何自定義自己需要的異常類
    1、為什麼需要自定義異常類?很簡單,java給我們提供的異常類不夠我們使用。2、那如何定義自己的異常類呢?廢話不多說,開始!添加一個帶異常信息的構造方法}(2)自定義異常類必須要繼承Exception|RuntimeException繼承Exception : 那麼自定義的異常類是一個編譯期異常
  • java如何通過異常信息定位問題原因
    最近在百度知道中回答一些問題時發現,發現很多軟體開發的新手,在遇到拋出的異常信息時就慌了,手足無措,不知道該怎麼處理,開始到處求助。今天就聊聊怎麼通過異常信息來定位問題原因吧。異常信息注意看紅框圈起來的這個異常,已經明確的提示了拋出異常的是TestException.java的第4行。那我們來看看這第四行是什麼吧。
  • 面試官:JAVA類的加載機制
    一、類的加載機制虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。3、 在Java堆中生成一個代表這個類的java.lang.Class對象,作為方法區這些數據的訪問入口。
  • 「007期」JavaSE面試題(七):異常
    Exception(異常):表示程序可以處理的異常,可以捕獲且可能恢復。遇到這類異常,應該儘可能處理異常,使程序恢復運行,而不應該隨意終止異常。(3)java.lang.NumberFormatException 字符串轉換為數字異常;出現原因:字符型數據中包含非數字型字符。(4)java.lang.IndexOutOfBoundsException 數組角標越界異常,常見於操作數組對象時發生。
  • 從頭開始:Java中的異常(定義檢查異常還是非檢查異常)
    廢話少說,今天來看看Java的異常處理。Java的異常談論Java的異常之前,先看看異常處理程序能帶來什麼好處?明顯的好處是降低錯誤處理代碼的複雜度。Java中的異常處理機制能幫忙開發者簡化處理異常的成本,如果發生異常了可以有兩種方式處理,第一種為捕捉異常並嘗試恢復,比如網絡異常,超時異常等,另一種就是向高層調用拋出異常。
  • JAVA 開發 SpringBoot RestTemplate自定義請求失敗異常處理
    二、源碼解析-默認實現首先程序中99%的異常都是可以自定義處理的,RestTemplate請求結果的異常自然也是可以自定義處理的。在開始自定義之前,我們來探究一下異常的默認處理實現,以此來說明為什麼會出現這樣的現象?
  • java面試中必問的oom問題
    總覽來看一下思維導圖,這些錯誤的異常你有遇到過嗎?常見的OOM1.2. java.lang.OutOfMemoryError: Java heap space溢出原因:深入理解Java虛擬機書中講到java堆溢出,Java堆用於存儲對象實例,只要不斷地創建對象,並且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,那麼在對象數量到達最大堆的容量限制後就會產生內存溢出異常
  • Java:如何更優雅地處理空值?
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫導語在筆者幾年的開發經驗中,經常看到項目中存在到處空值判斷的情況,這些判斷,會讓人覺得摸不著頭緒,它的出現很有可能和當前的業務邏輯並沒有關係。
  • 異常—詳解
    1)異常定義異常是指在程序在執行過程中,JVM的非正常停止,Java中異常本身就是一個類,產生異常意味著創建並拋出了一個異常對象 ,JVM處理異常的方法就是終止程序。2)異常體系Exception:編譯期異常,進行編譯(寫代碼)java程序出現的問題手搓RuntimeException:運行期異常,java程序運行過程中出現的問題JVM