Java 知識(shí)難點(diǎn)之異常的認(rèn)知與使用詳解
前言
本篇文章你會(huì)學(xué)習(xí)到什么是異常,異常的基本語(yǔ)法使用,和自定義異常,干貨多多?。。?/p>
一、 異常的背景
初識(shí)異常
我們?cè)?jīng)的代碼中已經(jīng)接觸了一些 “異常” 了. 例如
除以 0
public static void main(String[] args) { System.out.println(10 / 0); }
算術(shù)異常:
數(shù)組下標(biāo)越界
數(shù)組越界
int[] arr = {1, 2, 3}; System.out.println(arr[100]); // 執(zhí)行結(jié)果 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
訪問(wèn) null 對(duì)象
空指針異常
public class Test { public int num = 10; public static void main(String[] args) { Test t = null; System.out.println(t.num); } } // 執(zhí)行結(jié)果 Exception in thread "main" java.lang.NullPointerException
異常分為2種:
運(yùn)行時(shí)異常(非受查異常)
- 算數(shù)異常 數(shù)組越界異常 空指針異常 都是程序運(yùn)行的過(guò)程當(dāng)中發(fā)生的異常
編譯時(shí)異常(受查異常)
- 比如使用clone方法 在編譯前就劃紅線了就是編譯時(shí)異常
異常體系:
我們來(lái)看一下空指針異常(其實(shí)是一個(gè)類(lèi)繼承了運(yùn)行時(shí)異常)
對(duì)應(yīng)上面的圖,
在看一下運(yùn)行時(shí)異常:繼承了Exception(也可以看上面的圖)
看一下Exception 繼承了Throwable:
通過(guò)這個(gè)圖我們得到一個(gè)結(jié)論:每一個(gè)異常都是一個(gè)類(lèi),異常之間的關(guān)系 參考圖上繼承
我們看看Error
這個(gè)就叫做錯(cuò)誤
異常和錯(cuò)誤的區(qū)別:
錯(cuò)誤:必須由程序員處理邏輯錯(cuò)誤
異常:處理異常就OK了接下來(lái)繼續(xù)看
防御式編程:
錯(cuò)誤在代碼中是客觀存在的. 因此我們要讓程序出現(xiàn)問(wèn)題的時(shí)候及時(shí)通知程序猿. 我們有兩種主要的方式
LBYL: Look Before You Leap. 在操作之前就做充分的檢查.
EAFP: It's Easier to Ask Forgiveness than Permission. “事后獲取原諒比事前獲取許可更容易”. 也就是先操作, 遇到問(wèn)題再處理.
注意!!! 上面這兩個(gè)概念千萬(wàn)不要背!
其實(shí)很好理解, 舉個(gè)栗子~~
比如老濕年輕的時(shí)候, 和你們師娘剛開(kāi)始談對(duì)象. 我們都知道, 談對(duì)象需要有一些親密的動(dòng)作, 比如 “拉小手” 這 種. emmmmm 問(wèn)題來(lái)了, 老濕去拉師娘的小手有兩種方式:
a) 老濕說(shuō), 妹子, 我拉你小手可以嘛? 獲取妹子的同意后, 再拉手(這就是 LBYL).
b) 老濕趁妹子不備, 直接拉住. 大不了妹子生氣了給老濕一巴掌, 老濕再道歉就是(這就是 EAFP).
異常的核心思想就是 EAFP.
異常的好處
例如, 我們用偽代碼演示一下開(kāi)始一局王者榮耀的過(guò)程.
LBYL 風(fēng)格的代碼(不使用異常)
boolean ret = false; ret = 登陸游戲(); if (!ret) { 處理登陸游戲錯(cuò)誤; return; } ret = 開(kāi)始匹配(); if (!ret) { 處理匹配錯(cuò)誤; return; } ret = 游戲確認(rèn)(); if (!ret) { 處理游戲確認(rèn)錯(cuò)誤; return; } ret = 選擇英雄(); if (!ret) { 處理選擇英雄錯(cuò)誤; return; } ret = 載入游戲畫(huà)面(); if (!ret) { 處理載入游戲錯(cuò)誤; return; }
EAFP 風(fēng)格的代碼(使用異常)
try { 登陸游戲(); 開(kāi)始匹配(); 游戲確認(rèn)(); 選擇英雄(); 載入游戲畫(huà)面(); ... } catch (登陸游戲異常) { 處理登陸游戲異常; } catch (開(kāi)始匹配異常) { 處理開(kāi)始匹配異常; } catch (游戲確認(rèn)異常) { 處理游戲確認(rèn)異常; } catch (選擇英雄異常) { 處理選擇英雄異常; } catch (載入游戲畫(huà)面異常) { 處理載入游戲畫(huà)面異常; }
對(duì)比兩種不同風(fēng)格的代碼, 我們可以發(fā)現(xiàn), 使用第一種方式, 正常流程和錯(cuò)誤處理流程代碼混在一起, 代碼整體顯的比較混亂. 而第二種方式正常流程和錯(cuò)誤流程是分離開(kāi)的, 更容易理解代碼
二、異常的基本用法
基本語(yǔ)法
- try 代碼塊中放的是可能出現(xiàn)異常的代碼.
- catch 代碼塊中放的是出現(xiàn)異常后的處理行為.
- finally 代碼塊中的代碼用于處理善后工作, 會(huì)在最后執(zhí)行.
- 其中 catch 和 finally 都可以根據(jù)情況選擇加或者不加.
代碼:
public static void main(String[] args) { int[] arr = {1, 2, 3}; System.out.println("before"); System.out.println(arr[100]); System.out.println("after"); }
解釋?zhuān)?/p>
我們發(fā)現(xiàn)一旦出現(xiàn)異常, 程序就終止了. after 沒(méi)有正確輸出
為什么?
當(dāng)沒(méi)有處理異常的時(shí)候一旦程序發(fā)生了異常之后,這個(gè)異常會(huì)交給jvm,如果給jvm處理異常,那么程序一定會(huì)終止。
我們來(lái)自己處理異常:catch一定要捕獲相應(yīng)的異常(沒(méi)有捕獲到就交給了JVM了)
結(jié)果:
但是下面的也不會(huì)執(zhí)行了
相比上面的我們處理了異常,讓程序可以繼續(xù)運(yùn)行下去了
那上面是沒(méi)有異常消息的提示了,我們還想要些提示怎么搞呢??
使用:e.printStackTrace();
after還是正常出來(lái)
這個(gè)紅字可以進(jìn)行參考。
代碼示例 catch 可以有多個(gè):
一段代碼可能會(huì)拋出多種不同的異常, 不同的異常有不同的處理方式. 因此可以搭配多個(gè) catch 代碼塊.
如果多個(gè)異常的處理方式是完全相同, 也可以寫(xiě)成這樣
代碼示例 也可以用一個(gè) catch 捕獲所有異常(不推薦)
int[] arr = {1, 2, 3}; try { System.out.println("before"); arr = null; System.out.println(arr[100]); System.out.println("after"); } catch (Exception e) { e.printStackTrace(); } System.out.println("after try catch");
為什么不推薦了,因?yàn)閑xception 范圍太大了,不好排查
代碼示例 finally 的執(zhí)行不需要條件
int[] arr = {1, 2, 3}; try { System.out.println("before"); arr = null; System.out.println(arr[100]); System.out.println("after"); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("finally code"); }
finally 無(wú)論有沒(méi)有異常都會(huì)最后執(zhí)行
代碼示例 使用 try 負(fù)責(zé)回收資源
Scanner.close()可以釋放資源可以寫(xiě)到finally里面,也可以直接寫(xiě)到try里面
try (Scanner sc = new Scanner(System.in)) { int num = sc.nextInt(); System.out.println("num = " + num); } catch (Exception e) { e.printStackTrace(); }
代碼示例 如果向上一直傳遞都沒(méi)有合適的方法處理異常, 最終就會(huì)交給 JVM 處理, 程序就會(huì)異常終止(和我們最開(kāi)始未使用 try catch 時(shí)是一樣的).
public static void main(String[] args) { try { func(); } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); } System.out.println("after try catch"); } public static void func() { int[] arr = {1, 2, 3}; System.out.println(arr[100]); } // 直接結(jié)果 java.lang.ArrayIndexOutOfBoundsException: 100 at demo02.Test.func(Test.java:18) at demo02.Test.main(Test.java:9) after try catch
可以看見(jiàn)上面代碼是func可以有異常,但是在main方法里面處理了的
異常處理流程
- 程序先執(zhí)行 try 中的代碼
- 如果 try 中的代碼出現(xiàn)異常, 就會(huì)結(jié)束 try 中的代碼, 看和 catch 中的異常類(lèi)型是否匹配.
- 如果找到匹配的異常類(lèi)型, 就會(huì)執(zhí)行 catch 中的代碼
- 如果沒(méi)有找到匹配的異常類(lèi)型, 就會(huì)將異常向上傳遞到上層調(diào)用者.
- 無(wú)論是否找到匹配的異常類(lèi)型, finally 中的代碼都會(huì)被執(zhí)行到(在該方法結(jié)束之前執(zhí)行).
- 如果上層調(diào)用者也沒(méi)有處理的了異常, 就繼續(xù)向上傳遞.
- 一直到 main 方法也沒(méi)有合適的代碼處理異常, 就會(huì)交給 JVM 來(lái)進(jìn)行處理, 此時(shí)程序就會(huì)異常終止.
關(guān)于異常的處理方式
異常的種類(lèi)有很多, 我們要根據(jù)不同的業(yè)務(wù)場(chǎng)景來(lái)決定.
對(duì)于比較嚴(yán)重的問(wèn)題(例如和算錢(qián)相關(guān)的場(chǎng)景), 應(yīng)該讓程序直接崩潰, 防止造成更嚴(yán)重的后果
對(duì)于不太嚴(yán)重的問(wèn)題(大多數(shù)場(chǎng)景), 可以記錄錯(cuò)誤日志, 并通過(guò)監(jiān)控報(bào)警程序及時(shí)通知程序猿
對(duì)于可能會(huì)恢復(fù)的問(wèn)題(和網(wǎng)絡(luò)相關(guān)的場(chǎng)景), 可以嘗試進(jìn)行重試.
在我們當(dāng)前的代碼中采取的是經(jīng)過(guò)簡(jiǎn)化的第二種方式. 我們記錄的錯(cuò)誤日志是出現(xiàn)異常的方法調(diào)用信息, 能很快
速的讓我們找到出現(xiàn)異常的位置. 以后在實(shí)際工作中我們會(huì)采取更完備的方式來(lái)記錄異常信息
拋出異常
除了 Java 內(nèi)置的類(lèi)會(huì)拋出一些異常之外, 程序猿也可以手動(dòng)拋出某個(gè)異常. 使用 throw 關(guān)鍵字完成這個(gè)操作
public static void main(String[] args) { System.out.println(divide(10, 0)); } public static int divide(int x, int y) { if (y == 0) { throw new ArithmeticException("拋出除 0 異常"); } return x / y; } // 執(zhí)行結(jié)果 Exception in thread "main" java.lang.ArithmeticException: 拋出除 0 異常 at demo02.Test.divide(Test.java:14) at demo02.Test.main(Test.java:9)
在這個(gè)代碼中, 我們可以根據(jù)實(shí)際情況來(lái)拋出需要的異常. 在構(gòu)造異常對(duì)象同時(shí)可以指定一些描述性信息.
異常說(shuō)明
我們?cè)谔幚懋惓5臅r(shí)候, 通常希望知道這段代碼中究竟會(huì)出現(xiàn)哪些可能的異常.
我們可以使用 throws 關(guān)鍵字, 把可能拋出的異常顯式的標(biāo)注在方法定義的位置. 從而提醒調(diào)用者要注意捕獲這些異常.
public static int divide(int x, int y) throws ArithmeticException { if (y == 0) { throw new ArithmeticException("拋出除 0 異常"); } return x / y; }
三、 自定義異常類(lèi)
Java 中雖然已經(jīng)內(nèi)置了豐富的異常類(lèi), 但是我們實(shí)際場(chǎng)景中可能還有一些情況需要我們對(duì)異常類(lèi)進(jìn)行擴(kuò)展, 創(chuàng)建符合我們實(shí)際情況的異常.
我們先看一下其他的異常大概是個(gè)怎么樣的一個(gè)體系:
空指針異常是繼承了個(gè)運(yùn)行時(shí)異常,不過(guò)他里面的方法寫(xiě)的不是很多,也就是兩個(gè)幫父類(lèi)的構(gòu)造方法,所以按照它這樣的我們也可以寫(xiě)一個(gè)自己的異常。
創(chuàng)建一個(gè)異常類(lèi):
使用:
結(jié)果:
以上就是我們的一個(gè)自定義異常
那么我們可不可以繼承Exception呢?
這里發(fā)現(xiàn)報(bào)錯(cuò)了,為什么?我們?cè)趤?lái)看一下異常體系結(jié)構(gòu)
這個(gè)時(shí)候編譯器不知道是編譯時(shí)異常還是運(yùn)行時(shí)異常,所以默認(rèn)選擇權(quán)限小的編譯時(shí)異常,這個(gè)時(shí)候我們要拋出異常
上面的沒(méi)有報(bào)錯(cuò)了下面的開(kāi)始了?為什么?因?yàn)槲覀儝伋鼍幾g時(shí)異常,我們要try catch一下:
所以這個(gè)就是一個(gè)自定義異常。
在舉一個(gè)例子:
例如, 我們實(shí)現(xiàn)一個(gè)用戶登陸功能:(如果用戶名錯(cuò)誤處理用戶名的錯(cuò)誤,密碼錯(cuò)誤處理密碼錯(cuò)誤)
此時(shí)我們?cè)谔幚碛脩裘艽a錯(cuò)誤的時(shí)候可能就需要拋出兩種異常. 我們可以基于已有的異常類(lèi)進(jìn)行擴(kuò)展(繼承), 創(chuàng)建和我們業(yè)務(wù)相關(guān)的異常類(lèi)
自己寫(xiě)的類(lèi)
我們可以在login里面這樣寫(xiě):
主方法:
這樣就是使用我們自己的異常。
注意事項(xiàng):
- 自定義異常通常會(huì)繼承自 Exception 或者 RuntimeException
- 繼承自 Exception 的異常默認(rèn)是受查異常
- 繼承自 RuntimeException 的異常默認(rèn)是非受查異常.
以上就是異常的全部?jī)?nèi)容了,如果有什么不對(duì)的地方歡迎評(píng)論指正謝謝??!
到此這篇關(guān)于Java 知識(shí)難點(diǎn)之異常的認(rèn)知與使用詳解的文章就介紹到這了,更多相關(guān)Java 異常內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中字符串轉(zhuǎn)int數(shù)據(jù)類(lèi)型的三種方式
這篇文章主要介紹了Java中字符串轉(zhuǎn)int數(shù)據(jù)類(lèi)型的三種方式,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03在SpringBoot項(xiàng)目中解決依賴沖突問(wèn)題的方法
在SpringBoot項(xiàng)目中,依賴沖突是一個(gè)常見(jiàn)的問(wèn)題,特別是當(dāng)項(xiàng)目引入多個(gè)第三方庫(kù)或框架時(shí),依賴沖突可能導(dǎo)致編譯錯(cuò)誤、運(yùn)行時(shí)異?;虿豢深A(yù)測(cè)的行為,本文給大家介紹了如何在SpringBoot項(xiàng)目中解決以來(lái)沖突問(wèn)題的方法,需要的朋友可以參考下2024-01-01java并發(fā)編程synchronized底層實(shí)現(xiàn)原理
這篇文章主要介紹了java并發(fā)編程synchronized底層實(shí)現(xiàn)原理2022-02-02Java并發(fā)編程之線程池實(shí)現(xiàn)原理詳解
池化思想是一種空間換時(shí)間的思想,期望使用預(yù)先創(chuàng)建好的對(duì)象來(lái)減少頻繁創(chuàng)建對(duì)象的性能開(kāi)銷(xiāo),java中有多種池化思想的應(yīng)用,例如:數(shù)據(jù)庫(kù)連接池、線程池等,下面就來(lái)具體講講2023-05-05Spring中BeanFactory?FactoryBean和ObjectFactory的三種的區(qū)別
關(guān)于FactoryBean?和?BeanFactory的對(duì)比文章比較多,但是對(duì)ObjectFactory的描述就比較少,今天我們對(duì)比下這三種的區(qū)別,感興趣的朋友跟隨小編一起看看吧2023-01-01基于Java Callable接口實(shí)現(xiàn)線程代碼實(shí)例
這篇文章主要介紹了基于Java Callable接口實(shí)現(xiàn)線程代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08Spring Cloud引入Eureka組件,完善服務(wù)治理
這篇文章主要介紹了Spring Cloud引入Eureka組件,完善服務(wù)治理的過(guò)程詳解,幫助大家更好的理解和使用spring cloud,感興趣的朋友可以了解下2021-02-02