Java異常處理及自定義異常詳細(xì)指南
1. 異常是什么
?? 異常:程序在運(yùn)行過(guò)程中產(chǎn)生的不正常情況
- 程序在運(yùn)行的時(shí)候,發(fā)生了一些不被預(yù)期的事件,從而沒(méi)有按照我們編寫(xiě)的代碼執(zhí)行,這就是異常。
舉一些例子:
(1)算術(shù)異常
System.out.println(5/0) // 執(zhí)行結(jié)果 Exception in thread "main" java.lang.ArithmeticException: / by zero
- 這個(gè)時(shí)候你的程序是可以正常編譯的,但是在運(yùn)行的時(shí)候,因?yàn)槟阌?做了除數(shù),會(huì)拋出 java.lang.ArithmeticException 的異常。
(2)數(shù)組越界異常
int[] arr = {1, 2, 3}; System.out.println(arr[100]) // 執(zhí)行結(jié)果 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
(3)空指針
int[] arr = null; System.out.println(arr.length); // 執(zhí)行結(jié)果 Exception in thread "main" java.lang.NullPointerException
(4)其他:
- 輸入了錯(cuò)誤的數(shù)據(jù),比如:程序需要的是int類(lèi)型數(shù)據(jù),而用戶(hù)輸入了一串字符串;
- 對(duì)象沒(méi)有初始化就調(diào)用:下面這段代碼就會(huì)提示空指針異常;
String str = null; int length = str.length();
要打開(kāi)的文件不存在;
網(wǎng)絡(luò)通信時(shí)連接中斷,或者JVM內(nèi)存溢出。
2. 異常體系結(jié)構(gòu)
異常種類(lèi)繁多,為了對(duì)不同異?;蛘咤e(cuò)誤進(jìn)行很好的分類(lèi)管理,Java內(nèi)部維護(hù)了一個(gè)異常的體系結(jié)構(gòu):
從上圖中可以看到:
- Throwable:是異常體系的頂層類(lèi),其派生出兩個(gè)重要的子類(lèi),Error 和 Exception
- Error:指的是Java虛擬機(jī)無(wú)法解決的嚴(yán)重問(wèn)題,比如:JVM的內(nèi)部錯(cuò)誤、資源耗盡等。典型代表:StackOverflowError和OutOfMemoryError,一旦發(fā)生回力乏術(shù)。
- Exception:異常產(chǎn)生后程序員可以通過(guò)代碼進(jìn)行處理,使程序繼續(xù)執(zhí)行。比如:感冒、發(fā)燒。我們平時(shí)所說(shuō)的異常就是 Exception
- Exception的直接子類(lèi):編譯時(shí)異常,要求程序員在編寫(xiě)程序階段必須預(yù)先對(duì)這些異常進(jìn)行處理,如果不處理編譯器報(bào)錯(cuò),因此得名編譯時(shí)異常
- RuntimeException:運(yùn)行時(shí)異常。在編寫(xiě)程序階段程序員可以預(yù)先處理,也可以不管,都行
3. 異常的分類(lèi)
編譯時(shí)異常和運(yùn)行時(shí)異常,都發(fā)生在運(yùn)行階段。編譯階段異常是不會(huì)發(fā)生的。
異??赡茉诰幾g時(shí)發(fā)生,也可能在程序運(yùn)行時(shí)發(fā)生,根據(jù)發(fā)生的時(shí)機(jī)不同,可以將異常分為:
3.1 編譯時(shí)異常
在程序編譯期間發(fā)生的異常,稱(chēng)為編譯時(shí)異常,也稱(chēng)為 受檢查異常 (Checked Exception)
編譯時(shí)異常因?yàn)槭裁炊妹?/strong>
因?yàn)榫幾g時(shí)異常必須在編譯(編寫(xiě))階段預(yù)先處理,如果不處理編譯器報(bào)錯(cuò),因此得名
所有異常都是運(yùn)行階段發(fā)生的。因?yàn)橹挥谐绦蜻\(yùn)行階段才可以new對(duì)象
因?yàn)楫惓5陌l(fā)生都是new異常對(duì)象
class Person { private String name; private String gender; int age; // 想要讓該類(lèi)支持深拷貝,覆寫(xiě)Object類(lèi)的clone方法即可 @Override public Person clone() { return (Person)super.clone(); } } 編譯時(shí)報(bào)錯(cuò): Error:(17, 35) java: 未報(bào)告的異常錯(cuò)誤java.lang.CloneNotSupportedException; 必須對(duì)其進(jìn)行捕獲或聲明以便拋出
3.2 運(yùn)行時(shí)異常
在程序執(zhí)行期間發(fā)生的異常,稱(chēng)為運(yùn)行時(shí)異常,也稱(chēng)為非受檢查異常(Unchecked Exception)RunTimeException以及其子類(lèi)對(duì)應(yīng)的異常,都稱(chēng)為運(yùn)行時(shí)異常。
比如:NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException。
ClassCastException(類(lèi)轉(zhuǎn)換異常) IndexOutOfBoundsException(數(shù)組越界) NullPointerException(空指針) ArrayStoreException(數(shù)據(jù)存儲(chǔ)異常,操作數(shù)組時(shí)類(lèi)型不一致)
運(yùn)行時(shí)指的是程序已經(jīng)編譯通過(guò)得到 class 文件了,再由 JVM 執(zhí)行過(guò)程中出現(xiàn)的錯(cuò)誤
??注意:異常是Java中的錯(cuò)誤,但是并不是所有的錯(cuò)誤都是異常。比如:編譯時(shí)出現(xiàn)的語(yǔ)法性錯(cuò)誤,不能稱(chēng)之為異常。你在定義變量名的時(shí)候沒(méi)有依照Java的規(guī)則,在語(yǔ)句的結(jié)尾少了一個(gè)分號(hào),那么運(yùn)行出來(lái)結(jié)果是提示是錯(cuò)誤 java.lang.Error;這是 "編譯期" 出錯(cuò)。
3.3 區(qū)別
編譯時(shí)異常和運(yùn)行時(shí)異常的區(qū)別是什么?
- 編譯時(shí)異常一般發(fā)生的概率比較高。對(duì)于一些發(fā)生概率高的異常,需要在編譯時(shí)預(yù)先對(duì)其處理
- 運(yùn)行時(shí)異常一般發(fā)生的概率很低。你可以預(yù)先處理,也可以不處理
假設(shè)java中沒(méi)有對(duì)異常進(jìn)行劃分,沒(méi)有分編譯時(shí)異常和運(yùn)行時(shí)異常,所有的異常都需要在編寫(xiě)階段對(duì)其預(yù)處理,將是怎樣的效果?
- 首先,如果這樣做的話(huà),程序肯定是絕對(duì)的安全。
- 但是程序員就太累,到處都是處理異常的代碼,顯得很亂。
4. 異常的處理
4.1 防御式編程
錯(cuò)誤在代碼中是客觀(guān)存在的. 因此我們要讓程序出現(xiàn)問(wèn)題的時(shí)候及時(shí)通知程序猿. 主要的方式:
(1) LBYL: Look Before You Leap. 在操作之前就做充分的檢查. 即:事前防御型
- 缺陷:正常流程和錯(cuò)誤處理流程代碼混在一起, 代碼整體顯的比較混亂。
(2) EAFP: It's Easier to Ask Forgiveness than Permission. "事后獲取原諒比事前獲取許可更容易". 也就是先操作, 遇到問(wèn)題再處理. 即:事后認(rèn)錯(cuò)型
優(yōu)勢(shì):正常流程和錯(cuò)誤流程是分離開(kāi)的, 程序員更關(guān)注正常流程,代碼更清晰,容易理解代碼
異常處理的核心思想就是 EAFP。
在Java中,異常處理主要的5個(gè)關(guān)鍵字:throw、try、catch、final、throws。
4.2 異常的拋出
在編寫(xiě)程序時(shí),如果程序中出現(xiàn)錯(cuò)誤,此時(shí)就需要將錯(cuò)誤的信息告知給調(diào)用者,比如:參數(shù)檢測(cè)。
在Java中,可以借助 throw關(guān)鍵字
- 主動(dòng)拋出一個(gè)指定的異常對(duì)象,將錯(cuò)誤信息告知給調(diào)用者。
- 具體語(yǔ)法如下:
throw new XXXException("異常產(chǎn)生的原因");
首先我們來(lái)看系統(tǒng)自動(dòng)拋出異常:
public static void main(String[] args) { int a = 10; int b = 0; System.out.println(a/b); }
運(yùn)行這段代碼系統(tǒng)會(huì)自動(dòng)拋出 java.lang.ArithmeticException 異常。
這段程序使用 throw關(guān)鍵字也可以實(shí)現(xiàn):
public static void main(String[] args) { int a = 10; int b = 0; if(b == 0){ throw new ArithmeticException("/ by zero"); } System.out.println(a/b); }
throw是語(yǔ)句拋出一個(gè)異常,一般是在代碼塊的內(nèi)部,當(dāng)程序出現(xiàn)某種邏輯錯(cuò)誤時(shí)由程序員主動(dòng)拋出某種特定類(lèi)型的異常。
【注意事項(xiàng)】
- throw必須寫(xiě)在方法體內(nèi)部
- 拋出的對(duì)象必須是 Exception 或者 Exception 的子類(lèi)對(duì)象
- 如果拋出的是 RuntimeException 或者 RuntimeException 的子類(lèi),則可以不用處理,直接交給JVM來(lái)處理
- 如果拋出的是編譯時(shí)異常,用戶(hù)必須處理,否則無(wú)法通過(guò)編譯
- 異常一旦拋出,其后的代碼就不會(huì)執(zhí)行
- 使用 throw關(guān)鍵字主動(dòng)拋出檢測(cè)性異常的時(shí)候,在方法名上必須使用 throws表明調(diào)用這個(gè)方法可能存在要拋出的異常(異常聲明)
4.3 異常的捕獲
?? 異常的捕獲,也就是異常的具體處理方式,主要有兩種:異常聲明throws 以及 try-catch捕獲處理。
4.3.1 異常聲明 throws
?? 處在方法聲明時(shí)參數(shù)列表之后,當(dāng)方法中拋出編譯時(shí)異常,用戶(hù)不想處理該異常,此時(shí)就可以借助throws將異常拋給方法的調(diào)用者來(lái)處理。即當(dāng)前方法不處理異常,提醒方法的調(diào)用者處理異常.
語(yǔ)法格式: 修飾符 返回值類(lèi)型 方法名(參數(shù)列表) throws 異常類(lèi)型1,異常類(lèi)型2...{ }
?? 還記得我們異常拋出那里說(shuō)的:使用throw關(guān)鍵字主動(dòng)拋出檢測(cè)性異常的時(shí)候,在方法名上必須使用throws表明調(diào)用這個(gè)方法可能存在要拋出的異常(異常聲明)
舉個(gè)例子:
public static void test1(int a,int b){ if(b == 0) throw new ArithmeticException("/by zero"); System.out.println(a/b); } public static void test2(int a,int b) throws FileNotFoundException{ throw new FileNotFoundException(); }
- ArithmeticException屬于運(yùn)行時(shí)異常,是在運(yùn)行時(shí)檢測(cè)的,所以上述代碼編譯是能通過(guò)的
- FileNotFoundException是屬于檢測(cè)型異常,是在編譯之前就需要處理的,所以第二段程序要加上throws才能通過(guò)編譯。
【注意事項(xiàng)】
- throws必須跟在方法的參數(shù)列表之后
- 聲明的異常必須是 Exception 或者 Exception 的子類(lèi)對(duì)象
- 方法內(nèi)部如果拋出了多個(gè)異常,throws之后必須跟多個(gè)異常類(lèi)型,之間用逗號(hào)隔開(kāi),如果拋出多個(gè)異常類(lèi)型具有父子關(guān)系,直接聲明父類(lèi)即可。
- 調(diào)用聲明拋出異常的方法時(shí),調(diào)用者必須對(duì)該異常進(jìn)行處理,或者繼續(xù)使用throws拋出
說(shuō)到這個(gè)檢測(cè)型異常,我們需要了解一下常見(jiàn)的檢測(cè)型異常和非檢測(cè)型異常
4.3.2 常用的異常類(lèi)
非檢測(cè)型異常:
異常 | 描述 |
ArithmeticException | 當(dāng)出現(xiàn)異常的運(yùn)算條件時(shí),拋出此異常。例如,一個(gè)整數(shù)"除以零"時(shí),拋出此類(lèi)的一個(gè)實(shí)例 |
ArrayIndexOutOfBoundsException | 用非法索引訪(fǎng)問(wèn)數(shù)組時(shí)拋出的異常。如果索引為負(fù)或大于等于數(shù)組大小,則該索引為非法索引 |
ClassCastException | 當(dāng)試圖將對(duì)象強(qiáng)制轉(zhuǎn)換為不是實(shí)例的子類(lèi)時(shí),拋出該異常 |
IllegalArgumentException | 拋出的異常表明向方法傳遞了一個(gè)不合法或不正確的參數(shù) |
IllegalMonitorStateException | 拋出的異常表明某一線(xiàn)程已經(jīng)試圖等待對(duì)象的監(jiān)視器,或者試圖通知其他正在等待對(duì)象的監(jiān)視器而本身沒(méi)有指定監(jiān)視器的線(xiàn)程 |
IllegalStateException | 在非法或不適當(dāng)?shù)臅r(shí)間調(diào)用方法時(shí)產(chǎn)生的信號(hào)。換句話(huà)說(shuō),即 Java 環(huán)境或 Java 應(yīng)用程序沒(méi)有處于請(qǐng)求操作所要求的適當(dāng)狀態(tài)下 |
IllegalThreadStateException | 線(xiàn)程沒(méi)有處于請(qǐng)求操作所要求的適當(dāng)狀態(tài)時(shí)拋出的異常 |
IndexOutOfBoundsException | 指示某排序索引(例如對(duì)數(shù)組、字符串或向量的排序)超出范圍時(shí)拋出 |
NullPointerException | 當(dāng)應(yīng)用程序試圖在需要對(duì)象的地方使用 null 時(shí),拋出該異常 |
NumberFormatException | 當(dāng)應(yīng)用程序試圖將字符串轉(zhuǎn)換成一種數(shù)值類(lèi)型,但該字符串不能轉(zhuǎn)換為適當(dāng)格式時(shí),拋出該異常 |
StringIndexOutOfBoundsException | 此異常由 String 方法拋出,指示索引或者為負(fù),或者超出字符串的大小 |
檢測(cè)型異常:
異常 | 描述 |
ClassNotFoundException | 應(yīng)用程序試圖加載類(lèi)時(shí),找不到相應(yīng)的類(lèi),拋出該異常。 |
CloneNotSupportedException | 當(dāng)調(diào)用 Object 類(lèi)中的 clone 方法克隆對(duì)象,但該對(duì)象的類(lèi)無(wú)法實(shí)現(xiàn) Cloneable 接口時(shí),拋出該異常。 |
IllegalAccessException | 拒絕訪(fǎng)問(wèn)一個(gè)類(lèi)的時(shí)候,拋出該異常。 |
InstantiationException | 當(dāng)試圖使用 Class 類(lèi)中的 newInstance 方法創(chuàng)建一個(gè)類(lèi)的實(shí)例,而指定的類(lèi)對(duì)象因?yàn)槭且粋€(gè)接口或是一個(gè)抽象類(lèi)而無(wú)法實(shí)例化時(shí),拋出該異常。 |
InterruptedException | 一個(gè)線(xiàn)程被另一個(gè)線(xiàn)程中斷,拋出該異常。 |
NoSuchFieldException | 請(qǐng)求的變量不存在 |
NoSuchMethodException | 請(qǐng)求的方法不存在 |
IOException及其子類(lèi) | 對(duì)文件或流的操作有誤時(shí),拋出異常 |
4.3.3 try-catch捕獲并處理
?? throws 對(duì)異常并沒(méi)有真正處理,而是將異常報(bào)告給拋出異常方法的調(diào)用者,由調(diào)用者處理。如果真正要對(duì)異常進(jìn)行處理,就需要 try-catch
語(yǔ)法格式: try{ // 將可能出現(xiàn)異常的代碼放在這里 }catch(要捕獲的異常類(lèi)型 e){ // 如果try中的代碼拋出異常了,此處catch捕獲時(shí)異常類(lèi)型與try中拋出的異常類(lèi)型一致時(shí),或者是try中拋出異常的基類(lèi) 時(shí),就會(huì)被捕獲到 // 對(duì)異常就可以正常處理,處理完成后,跳出try-catch結(jié)構(gòu),繼續(xù)執(zhí)行后序代碼 }[catch(異常類(lèi)型 e){ // 對(duì)異常進(jìn)行處理 }finally{ // 此處代碼一定會(huì)被執(zhí)行到 }] // 后序代碼 // 當(dāng)異常被捕獲到時(shí),異常就被處理了,這里的后序代碼一定會(huì)執(zhí)行 // 如果捕獲了,由于捕獲時(shí)類(lèi)型不對(duì),那就沒(méi)有捕獲到,這里的代碼就不會(huì)被執(zhí)行
注意:
[]中表示可選項(xiàng),可以添加,也可以不用添加
try中的代碼可能會(huì)拋出異常,也可能不會(huì)
舉個(gè)例子:
private static void m1() throws FileNotFoundException { System.out.println("m1 begin"); m2(); // 以上代碼出異常,這里是無(wú)法執(zhí)行的。 System.out.println("m1 over"); }
try { m1(); // m1方法出異常,下面代碼不執(zhí)行。 System.out.println("hello world!");//不執(zhí)行 } catch (FileNotFoundException e){ //異常處理 System.out.println("出異常了??!"); System.out.println(e); } System.out.println("hello world"); //會(huì)執(zhí)行
【注意事項(xiàng)】
(1)try塊內(nèi)拋出異常位置之后的代碼將不會(huì)被執(zhí)行
(2)如果拋出異常類(lèi)型與catch時(shí)異常類(lèi)型不匹配,即異常不會(huì)被成功捕獲,也就不會(huì)被處理,繼續(xù)往外拋,直到JVM收到后中斷程序----異常是按照類(lèi)型來(lái)捕獲的
public static void main(String[] args) { try { int[] array = {1,2,3}; System.out.println(array[3]); // 此處會(huì)拋出數(shù)組越界異常 }catch (NullPointerException e){ // 捕獲時(shí)候捕獲的是空指針異常--真正的異常無(wú)法被捕獲到 e.printStackTrace(); } System.out.println("后序代碼"); } Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3 at day20210917.ArrayOperator.main(ArrayOperator.java:24)
(3)try中可能會(huì)拋出多個(gè)不同的異常對(duì)象,則必須用多個(gè)catch來(lái)捕獲----即多種異常,多次捕獲
public static void main(String[] args) { int[] arr = { 1, 2, 3 }; try { System.out.println("before"); //arr = null; System.out.println(arr[100]); System.out.println("after"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("這是個(gè)數(shù)組下標(biāo)越界異常"); e.printStackTrace(); } catch (NullPointerException e) { System.out.println("這是個(gè)空指針異常"); e.printStackTrace(); } System.out.println("after try catch"); } 如果多個(gè)異常的處理方式是完全相同, 也可以寫(xiě)成這樣: catch (ArrayIndexOutOfBoundsException | NullPointerException e) { ... }
- 如果異常之間具有父子關(guān)系,一定是子類(lèi)異常在前catch,父類(lèi)異常在后catch,否則語(yǔ)法錯(cuò)誤
public static void main(String[] args) { int[] arr = { 1, 2, 3 }; try { System.out.println("before"); arr = null; System.out.println(arr[100]); System.out.println("after"); } catch (Exception e) { // Exception可以捕獲到所有異常 e.printStackTrace(); } catch (NullPointerException e) { // 永遠(yuǎn)都捕獲執(zhí)行到 e.printStackTrace(); } System.out.println("after try catch"); }
(4) 可以通過(guò)一個(gè)catch捕獲所有的異常,即多個(gè)異常,一次捕獲(不推薦)
public static void main(String[] args) { 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"); }
- 由于 Exception 類(lèi)是所有異常類(lèi)的父類(lèi)。 因此可以用這個(gè)類(lèi)型表示捕捉所有異常.
- 補(bǔ)充:catch 進(jìn)行類(lèi)型匹配的時(shí)候,不光會(huì)匹配相同類(lèi)型的異常對(duì)象,也會(huì)捕捉目標(biāo)異常類(lèi)型的子類(lèi)對(duì)象.
- 如剛才的代碼,NullPointerException 和 ArrayIndexOutOfBoundsException 都是 Exception 的子類(lèi),因此都能被捕獲到.
4.3.4 finally
?? 在寫(xiě)程序時(shí),有些特定的代碼,不論程序是否發(fā)生異常,都需要執(zhí)行,比如程序中打開(kāi)的資源:網(wǎng)絡(luò)連接、數(shù)據(jù)庫(kù)連接、IO流等,在程序正?;蛘弋惓M顺鰰r(shí),必須要對(duì)資源進(jìn)進(jìn)行回收。另外,因?yàn)?strong>異常會(huì)引發(fā)程序的跳轉(zhuǎn),可能導(dǎo)致有些語(yǔ)句執(zhí)行不到,finally就是用來(lái)解決這個(gè)問(wèn)題的。
在finally子句中的代碼是最后執(zhí)行的,并且是 一定會(huì)執(zhí)行 的,即使try語(yǔ)句塊中的代碼出現(xiàn)了異常。
finally子句必須和try一起出現(xiàn),不能單獨(dú)編寫(xiě)。
語(yǔ)法格式: try{ // 可能會(huì)發(fā)生異常的代碼 }catch(異常類(lèi)型 e){ // 對(duì)捕獲到的異常進(jìn)行處理 }finally{ // 此處的語(yǔ)句無(wú)論是否發(fā)生異常,都會(huì)被執(zhí)行到 } // 如果沒(méi)有拋出異常,或者異常被捕獲處理了,這里的代碼也會(huì)執(zhí)行
案例1:finally語(yǔ)句通常使用在finally語(yǔ)句塊中完成 資源的釋放/關(guān)閉。
public static void main(String[] args) { try { int[] arr = { 1,2,3 }; arr[100] = 10; arr[0] = 10; } catch (ArrayIndexOutOfBoundsException e) { System.out.println(e); } finally { System.out.println("finally中的代碼一定會(huì)執(zhí)行"); } System.out.println("如果沒(méi)有拋出異常,或者異常被處理了,try-catch后的代碼也會(huì)執(zhí)行"); }
案例2:try和finally聯(lián)用,沒(méi)有catch
public static void main(String[] args) { try { System.out.println("try..."); return; } finally { System.out.println("finally..."); } // 這里不能寫(xiě)語(yǔ)句,因?yàn)檫@個(gè)代碼是無(wú)法執(zhí)行到的。 //System.out.println("Hello World!"); }
注意:
- try不能單獨(dú)使用。
- try finally可以聯(lián)合使用。
- 放在finally語(yǔ)句塊中的代碼是一定會(huì)執(zhí)行的,一般在finally中進(jìn)行一些資源清理的掃尾工作。
finally子句失效:System.exit(0)
;
只有這個(gè)可以治 finallypublic static void main(String[] args) { try { System.out.println("try..."); // 退出JVM System.exit(0); // 退出JVM之后,finally語(yǔ)句中的代碼就不執(zhí)行了! } finally { System.out.println("finally..."); } }
給大家看一個(gè)題:
// 下面程序輸出什么? public static void main(String[] args) { System.out.println(func()); } public static int func() { try { return 10; } finally { return 20; } } A: 10 B: 20 C: 30 D: 編譯失敗
finally 執(zhí)行的時(shí)機(jī)是在方法返回之前(try 或者 catch 中如果有 return 會(huì)在這個(gè) return 之前執(zhí)行 finally). 但是如果 finally 中也存在 return 語(yǔ)句, 那么就會(huì)執(zhí)行 finally 中的 return, 從而不會(huì)執(zhí)行到 try 中原有的 return
- 一般我們不建議在 finally 中寫(xiě) return (被編譯器當(dāng)做一個(gè)警告).
4.4 異常處理流程
首先在學(xué)習(xí)這個(gè)之前,我們先來(lái)了解什么是 "調(diào)用棧" ?
- 方法之間是存在相互調(diào)用關(guān)系的, 這種調(diào)用關(guān)系我們可以用 "調(diào)用棧" 來(lái)描述. 在 JVM 中有一塊內(nèi)存空間稱(chēng)為"虛擬機(jī)棧" 專(zhuān)門(mén)存儲(chǔ)方法之間的調(diào)用關(guān)系. 當(dāng)代碼中出現(xiàn)異常的時(shí)候, 我們就可以使用 e.printStackTrace(); 的方式查看出現(xiàn)異常代碼的調(diào)用棧.
案例:
public static void main(String[] args) { try { func(); } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); } System.out.println("在 try catch 之后"); } public static void func() { int[] arr = { 1, 2, 3 }; System.out.println(arr[100]); // 指針越界 }
如果向上一直傳遞都沒(méi)有合適的方法處理異常, 最終就會(huì)交給 JVM 處理, 程序就會(huì)異常終止
- (和我們最開(kāi)始未使用 try catch 時(shí)是一樣的).
public static void main(String[] args) { func(); System.out.println("在 try catch 之后"); } public static void func() { int[] arr = { 1, 2, 3 }; System.out.println(arr[100]); // 指針越界 }
可以看到, 程序已經(jīng)異常終止了, 沒(méi)有執(zhí)行到 System.out.println("在 try catch 之后"); 這一行
【異常處理流程總結(jié)】
程序先執(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ì)異常終止.
5. 自定義異常
?? 前面談到的都是系統(tǒng)自帶的異常,但是如果我們是在開(kāi)發(fā)一個(gè)復(fù)雜項(xiàng)目,就經(jīng)常會(huì)遇到系統(tǒng)自帶的異常不能滿(mǎn)足我們的需求的情況,所以這個(gè)時(shí)候就需要我們自己來(lái)定義異常了。
使用自定義異常
?? 我們一般使用繼承Exception類(lèi)的方式來(lái)自定義異常,那具體怎么進(jìn)行呢?
很簡(jiǎn)單,我們只需要繼承Exception,再將信息傳遞給父類(lèi)就可以了:
class 自定義異常名 extends Exception{ //因?yàn)镋xception已經(jīng)實(shí)現(xiàn)了很多異常處理的方法了屬性了, //所以自定義異常只需要將信息傳遞給父類(lèi)(使用super關(guān)鍵字)即可 }
舉個(gè)例子:
- 定義一個(gè)自定義異常,判斷用戶(hù)名是否小于三位,如果用戶(hù)名小于三位,就拋出一個(gè)自定義異常。
public class Task { /********* Begin *********/ public static void main(String[] args) throws Exception{ Scanner sc = new Scanner(System.in); String username = sc.next(); //判斷用戶(hù)名 if(username.length() < 3){ throw new MyException("用戶(hù)名小于三位Exception"); } else System.out.println("用戶(hù)名格式正確"); } } class MyException extends Exception{ MyException(String s){ super(s); } }
注意事項(xiàng)
- 自定義異常通常會(huì)繼承自 Exception 或者 RuntimeException
- 繼承自 Exception 的異常默認(rèn)是受查異常
- 繼承自 RuntimeException 的異常默認(rèn)是非受查異常
6. 小結(jié)
?? 關(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)行重試.
一般來(lái)說(shuō)都會(huì)采取的是經(jīng)過(guò)簡(jiǎn)化的第二種方式. 我們記錄的錯(cuò)誤日志是出現(xiàn)異常的方法調(diào)用信息, 能很快速的讓我們找到出現(xiàn)異常的位置. 以后在實(shí)際工作中我們會(huì)采取更完備的方式來(lái)記錄異常信息.
到此這篇關(guān)于Java異常處理及自定義異常的文章就介紹到這了,更多相關(guān)Java異常處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決Swagger2返回map復(fù)雜結(jié)構(gòu)不能解析的問(wèn)題
這篇文章主要介紹了解決Swagger2返回map復(fù)雜結(jié)構(gòu)不能解析的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07IDEA無(wú)法識(shí)別相關(guān)module模塊問(wèn)題的解決過(guò)程
這篇文章主要給大家介紹了關(guān)于IDEA無(wú)法識(shí)別相關(guān)module模塊問(wèn)題的解決過(guò)程,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用IDEA具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07Java實(shí)現(xiàn)冒泡排序簡(jiǎn)單示例
冒泡排序(Bubble Sort)是一種簡(jiǎn)單的排序算法,它重復(fù)地走訪(fǎng)過(guò)要排序的數(shù)列,一次比較兩個(gè)元素,如果他們的順序錯(cuò)誤就把他們交換過(guò)來(lái),下面這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)冒泡排序的相關(guān)資料,需要的朋友可以參考下2023-06-06使用maven?shade插件解決項(xiàng)目版本沖突詳解
這篇文章主要為大家介紹了使用maven?shade插件解決項(xiàng)目版本沖突詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09SpringBoot3使用?自定義注解+Jackson實(shí)現(xiàn)接口數(shù)據(jù)脫敏的步驟
本文介紹了一種以?xún)?yōu)雅的方式實(shí)現(xiàn)對(duì)接口返回的敏感數(shù)據(jù),如手機(jī)號(hào)、郵箱、身份證等信息的脫敏處理,這種方法也是企業(yè)常用方法,話(huà)不多說(shuō)我們一起來(lái)看一下吧2024-03-03Java的常見(jiàn)熱門(mén)ORM框架優(yōu)缺點(diǎn)區(qū)別
Java?ORM框架是一種用于將Java對(duì)象映射到關(guān)系型數(shù)據(jù)庫(kù)中的工具,使得開(kāi)發(fā)人員能夠通過(guò)對(duì)象操作數(shù)據(jù)庫(kù)而不必直接使用SQL查詢(xún),Java開(kāi)發(fā)變得更加高效和易于維護(hù),選擇適合你的ORM框架是根據(jù)你的需求決定的,比如你的應(yīng)用場(chǎng)景,數(shù)據(jù)結(jié)構(gòu)和技術(shù)水平等2024-02-02詳解Spring Batch 輕量級(jí)批處理框架實(shí)踐
這篇文章主要介紹了詳解Spring Batch 輕量級(jí)批處理框架實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06Java開(kāi)發(fā)JUC交換器Exchanger使用詳解
這篇文章主要為大家介紹了Java開(kāi)發(fā)JUC交換器Exchanger使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Java中的 VO,BO,DO 對(duì)象命名問(wèn)題小結(jié)
本文講解VO,BO,DO 的作用以及如何使用,分析了如何消除三者之間重復(fù)的代碼,同樣結(jié)合現(xiàn)實(shí)生活中領(lǐng)導(dǎo)配秘書(shū)來(lái)類(lèi)比講解,對(duì)Java VO 對(duì)象命名相關(guān)知識(shí)感興趣的朋友一起看看吧2024-01-01