【转载】一文彻底拿下Java异常
文章目錄
- 1. 異常框架
- 1.1. Throwable
- 1.2. Error
- 1.3. Exception
- 常見 Exception:
- 1.4. RuntimeException
- 常見 RuntimeException:
- 2. 自定義異常
- 3. 拋出異常
- 4. 捕獲異常
- 5. 異常鏈
- 6. 異常注意事項(xiàng)
- 7. 最佳實(shí)踐
1. 異常框架
1.1. Throwable
**Throwable 是 Java 語(yǔ)言中所有錯(cuò)誤(Error)和異常(Exception)的超類。**在 Java 中只有 Throwable 類型的實(shí)例才可以被拋出(throw)或者捕獲(catch),它是異常處理機(jī)制的基本組成類型。
Throwable 包含了其線程創(chuàng)建時(shí)線程執(zhí)行堆棧的快照,它提供了 printStackTrace() 等接口用于獲取堆棧跟蹤數(shù)據(jù)等信息。
主要方法:
fillInStackTrace - 用當(dāng)前的調(diào)用棧層次填充 Throwable 對(duì)象棧層次,添加到棧層次任何先前信息中。
getMessage - 返回關(guān)于發(fā)生的異常的詳細(xì)信息。這個(gè)消息在 Throwable 類的構(gòu)造函數(shù)中初始化了。
getCause - 返回一個(gè) Throwable 對(duì)象代表異常原因。
getStackTrace - 返回一個(gè)包含堆棧層次的數(shù)組。下標(biāo)為 0 的元素代表?xiàng)m?#xff0c;最后一個(gè)元素代表方法調(diào)用堆棧的棧底。
printStackTrace - 打印 toString() 結(jié)果和棧層次到 System.err,即錯(cuò)誤輸出流。
toString - 使用 getMessage 的結(jié)果返回代表 Throwable 對(duì)象的字符串。
1.2. Error
Error 是 Throwable 的一個(gè)子類。Error 表示正常情況下,不大可能出現(xiàn)的嚴(yán)重問(wèn)題。編譯器不會(huì)檢查 Error。絕大部分的 Error 都會(huì)導(dǎo)致程序(比如 JVM 自身)處于非正常的、不可恢復(fù)狀態(tài)。既然是非正常情況,所以不便于也不需要捕獲,常見的比如 OutOfMemoryError 之類,都是 Error 的子類。
常見 Error:
AssertionError - 斷言錯(cuò)誤。
VirtualMachineError - 虛擬機(jī)錯(cuò)誤。
UnsupportedClassVersionError - Java 類版本錯(cuò)誤。
StackOverflowError - 棧溢出錯(cuò)誤。
OutOfMemoryError - 內(nèi)存溢出錯(cuò)誤。
1.3. Exception
Exception 是 Throwable 的一個(gè)子類。Exception 表示合理的應(yīng)用程序可能想要捕獲的條件。Exception 是程序正常運(yùn)行中,可以預(yù)料的意外情況,可能并且應(yīng)該被捕獲,進(jìn)行相應(yīng)處理。
Exception 又分為可檢查(checked)異常和不檢查(unchecked)異常,可檢查異常在源代碼里必須顯式地進(jìn)行捕獲處理,這是編譯期檢查的一部分。
編譯器會(huì)檢查 Exception 異常,此類異常,要么通過(guò) throws 進(jìn)行聲明拋出,要么通過(guò) try catch 進(jìn)行捕獲處理,否則不能通過(guò)編譯。
常見 Exception:
ClassNotFoundException - 應(yīng)用程序試圖加載類時(shí),找不到相應(yīng)的類,拋出該異常。
CloneNotSupportedException - 當(dāng)調(diào)用 Object 類中的 clone 方法克隆對(duì)象,但該對(duì)象的類無(wú)法實(shí)現(xiàn) Cloneable 接口時(shí),拋出該異常。
IllegalAccessException - 拒絕訪問(wèn)一個(gè)類的時(shí)候,拋出該異常。
InstantiationException - 當(dāng)試圖使用 Class 類中的 newInstance 方法創(chuàng)建一個(gè)類的實(shí)例,而指定的類對(duì)象因?yàn)槭且粋€(gè)接口或是一個(gè)抽象類而無(wú)法實(shí)例化時(shí),拋出該異常。
InterruptedException - 一個(gè)線程被另一個(gè)線程中斷,拋出該異常。
NoSuchFieldException - 請(qǐng)求的變量不存在。
NoSuchMethodException - 請(qǐng)求的方法不存在。
示例:
試圖編譯運(yùn)行時(shí)會(huì)報(bào)錯(cuò):
Error:(7, 47) java: 未報(bào)告的異常錯(cuò)誤java.lang.NoSuchMethodException; 必須對(duì)其進(jìn)行捕獲或聲明以便拋出
1.4. RuntimeException
RuntimeException 是 Exception 的一個(gè)子類。RuntimeException 是那些可能在 Java 虛擬機(jī)正常運(yùn)行期間拋出的異常的超類。
**編譯器不會(huì)檢查 RuntimeException 異常。**當(dāng)程序中可能出現(xiàn)這類異常時(shí),倘若既沒(méi)有通過(guò) throws 聲明拋出它,也沒(méi)有用 try catch 語(yǔ)句捕獲它,程序還是會(huì)編譯通過(guò)。
示例:
public class RuntimeExceptionDemo {public static void main(String[] args) {// 此處產(chǎn)生了異常int result = 10 / 0;System.out.println("兩個(gè)數(shù)字相除的結(jié)果:" + result);System.out.println("----------------------------");} };運(yùn)行時(shí)輸出:
Exception in thread “main” java.lang.ArithmeticException: / by zero
at
io.github.dunwu.javacore.exception.RumtimeExceptionDemo01.main(RumtimeExceptionDemo01.java:6)
常見 RuntimeException:
ArrayIndexOutOfBoundsException - 用非法索引訪問(wèn)數(shù)組時(shí)拋出的異常。如果索引為負(fù)或大于等于數(shù)組大小,則該索引為非法索引。
ArrayStoreException - 試圖將錯(cuò)誤類型的對(duì)象存儲(chǔ)到一個(gè)對(duì)象數(shù)組時(shí)拋出的異常。
ClassCastException - 當(dāng)試圖將對(duì)象強(qiáng)制轉(zhuǎn)換為不是實(shí)例的子類時(shí),拋出該異常。
IllegalArgumentException - 拋出的異常表明向方法傳遞了一個(gè)不合法或不正確的參數(shù)。
IllegalMonitorStateException - 拋出的異常表明某一線程已經(jīng)試圖等待對(duì)象的監(jiān)視器,或者試圖通知其他正在等待對(duì)象的監(jiān)視器而本身沒(méi)有指定監(jiān)視器的線程。
IllegalStateException - 在非法或不適當(dāng)?shù)臅r(shí)間調(diào)用方法時(shí)產(chǎn)生的信號(hào)。換句話說(shuō),即 Java 環(huán)境或 Java 應(yīng)用程序沒(méi)有處于請(qǐng)求操作所要求的適當(dāng)狀態(tài)下。
IllegalThreadStateException - 線程沒(méi)有處于請(qǐng)求操作所要求的適當(dāng)狀態(tài)時(shí)拋出的異常。
IndexOutOfBoundsException - 指示某排序索引(例如對(duì)數(shù)組、字符串或向量的排序)超出范圍時(shí)拋出。
NegativeArraySizeException - 如果應(yīng)用程序試圖創(chuàng)建大小為負(fù)的數(shù)組,則拋出該異常。
NullPointerException - 當(dāng)應(yīng)用程序試圖在需要對(duì)象的地方使用 null 時(shí),拋出該異常
NumberFormatException - 當(dāng)應(yīng)用程序試圖將字符串轉(zhuǎn)換成一種數(shù)值類型,但該字符串不能轉(zhuǎn)換為適當(dāng)格式時(shí),拋出該異常。
SecurityException - 由安全管理器拋出的異常,指示存在安全侵犯。
StringIndexOutOfBoundsException - 此異常由 String 方法拋出,指示索引或者為負(fù),或者超出字符串的大小。
UnsupportedOperationException - 當(dāng)不支持請(qǐng)求的操作時(shí),拋出該異常。
2. 自定義異常
自定義一個(gè)異常類,只需要繼承 Exception 或 RuntimeException 即可。
示例:
public class MyExceptionDemo {public static void main(String[] args) {throw new MyException("自定義異常");}static class MyException extends RuntimeException {public MyException(String message) {super(message);}} }輸出:
Exception in thread “main”
io.github.dunwu.javacore.exception.MyExceptionDemo$MyException: 自定義異常
at
io.github.dunwu.javacore.exception.MyExceptionDemo.main(MyExceptionDemo.java:9)
3. 拋出異常
如果想在程序中明確地拋出異常,需要用到 throw 和 throws 。
如果一個(gè)方法沒(méi)有捕獲一個(gè)檢查性異常,那么該方法必須使用 throws 關(guān)鍵字來(lái)聲明。throws 關(guān)鍵字放在方法簽名的尾部。
throw 示例:
public class ThrowDemo {public static void f() {try {throw new RuntimeException("拋出一個(gè)異常");} catch (Exception e) {System.out.println(e);}}public static void main(String[] args) {f();} };輸出:
java.lang.RuntimeException: 拋出一個(gè)異常
也可以使用 throw 關(guān)鍵字拋出一個(gè)異常,無(wú)論它是新實(shí)例化的還是剛捕獲到的。
throws 示例:
public class ThrowsDemo {public static void f1() throws NoSuchMethodException, NoSuchFieldException {Field field = Integer.class.getDeclaredField("digits");if (field != null) {System.out.println("反射獲取 digits 方法成功");}Method method = String.class.getMethod("toString", int.class);if (method != null) {System.out.println("反射獲取 toString 方法成功");}}public static void f2() {try {// 調(diào)用 f1 處,如果不用 try catch ,編譯時(shí)會(huì)報(bào)錯(cuò)f1();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();}}public static void main(String[] args) {f2();} };輸出:
反射獲取 digits 方法成功 java.lang.NoSuchMethodException:
java.lang.String.toString(int) at
java.lang.Class.getMethod(Class.java:1786) at
io.github.dunwu.javacore.exception.ThrowsDemo.f1(ThrowsDemo.java:12)
at
io.github.dunwu.javacore.exception.ThrowsDemo.f2(ThrowsDemo.java:21)
at
io.github.dunwu.javacore.exception.ThrowsDemo.main(ThrowsDemo.java:30)
throw 和 throws 的區(qū)別:
throws 使用在函數(shù)上,throw 使用在函數(shù)內(nèi)。
throws 后面跟異常類,可以跟多個(gè),用逗號(hào)區(qū)別;throw 后面跟的是異常對(duì)象。
4. 捕獲異常
使用 try 和 catch 關(guān)鍵字可以捕獲異常。try catch 代碼塊放在異常可能發(fā)生的地方。
它的語(yǔ)法形式如下:
try {// 可能會(huì)發(fā)生異常的代碼塊 } catch (Exception e1) {// 捕獲并處理try拋出的異常類型Exception } catch (Exception2 e2) {// 捕獲并處理try拋出的異常類型Exception2 } finally {// 無(wú)論是否發(fā)生異常,都將執(zhí)行的代碼塊 } //此外,JDK7 以后,catch 多種異常時(shí),也可以像下面這樣簡(jiǎn)化代碼:try {// 可能會(huì)發(fā)生異常的代碼塊 } catch (Exception | Exception2 e) {// 捕獲并處理try拋出的異常類型 } finally {// 無(wú)論是否發(fā)生異常,都將執(zhí)行的代碼塊 }try - try 語(yǔ)句用于監(jiān)聽。將要被監(jiān)聽的代碼(可能拋出異常的代碼)放在 try 語(yǔ)句塊之內(nèi),當(dāng) try 語(yǔ)句塊內(nèi)發(fā)生異常時(shí),異常就被拋出。
catch - catch 語(yǔ)句包含要捕獲異常類型的聲明。當(dāng)保護(hù)代碼塊中發(fā)生一個(gè)異常時(shí),try 后面的 catch 塊就會(huì)被檢查。
finally - finally 語(yǔ)句塊總是會(huì)被執(zhí)行,無(wú)論是否出現(xiàn)異常。try catch 語(yǔ)句后不一定非要finally 語(yǔ)句。finally 常用于這樣的場(chǎng)景:由于finally 語(yǔ)句塊總是會(huì)被執(zhí)行,所以那些在 try 代碼塊中打開的,并且必須回收的物理資源(如數(shù)據(jù)庫(kù)連接、網(wǎng)絡(luò)連接和文件),一般會(huì)放在finally 語(yǔ)句塊中釋放資源。
try、catch、finally 三個(gè)代碼塊中的局部變量不可共享使用。
catch 塊嘗試捕獲異常時(shí),是按照 catch 塊的聲明順序從上往下尋找的,一旦匹配,就不會(huì)再向下執(zhí)行。因此,如果同一個(gè) try 塊下的多個(gè) catch 異常類型有父子關(guān)系,應(yīng)該將子類異常放在前面,父類異常放在后面。
示例:
運(yùn)行時(shí)輸出:
出現(xiàn)異常了:java.lang.ArithmeticException: / by zero 不管是否出現(xiàn)異常,都執(zhí)行此代碼
5. 異常鏈
異常鏈?zhǔn)且砸粋€(gè)異常對(duì)象為參數(shù)構(gòu)造新的異常對(duì)象,新的異常對(duì)象將包含先前異常的信息。
通過(guò)使用異常鏈,我們可以提高代碼的可理解性、系統(tǒng)的可維護(hù)性和友好性。
我們有兩種方式處理異常,一是 throws 拋出交給上級(jí)處理,二是 try…catch 做具體處理。try…catch 的 catch 塊我們可以不需要做任何處理,僅僅只用 throw 這個(gè)關(guān)鍵字將我們封裝異常信息主動(dòng)拋出來(lái)。然后在通過(guò)關(guān)鍵字 throws 繼續(xù)拋出該方法異常。它的上層也可以做這樣的處理,以此類推就會(huì)產(chǎn)生一條由異常構(gòu)成的異常鏈。
【示例】
public class ExceptionChainDemo {static class MyException1 extends Exception {public MyException1(String message) {super(message);}}static class MyException2 extends Exception {public MyException2(String message, Throwable cause) {super(message, cause);}}public static void f1() throws MyException1 {throw new MyException1("出現(xiàn) MyException1");}public static void f2() throws MyException2 {try {f1();} catch (MyException1 e) {throw new MyException2("出現(xiàn) MyException2", e);}}public static void main(String[] args) throws MyException2 {f2();} }輸出:
Exception in thread “main”
io.github.dunwu.javacore.exception.ExceptionChainDemoMyException2:出現(xiàn)MyException2atio.github.dunwu.javacore.exception.ExceptionChainDemo.f2(ExceptionChainDemo.java:29)atio.github.dunwu.javacore.exception.ExceptionChainDemo.main(ExceptionChainDemo.java:34)Causedby:io.github.dunwu.javacore.exception.ExceptionChainDemoMyException2: 出現(xiàn) MyException2 at io.github.dunwu.javacore.exception.ExceptionChainDemo.f2(ExceptionChainDemo.java:29) at io.github.dunwu.javacore.exception.ExceptionChainDemo.main(ExceptionChainDemo.java:34) Caused by: io.github.dunwu.javacore.exception.ExceptionChainDemoMyException2:出現(xiàn)MyException2atio.github.dunwu.javacore.exception.ExceptionChainDemo.f2(ExceptionChainDemo.java:29)atio.github.dunwu.javacore.exception.ExceptionChainDemo.main(ExceptionChainDemo.java:34)Causedby:io.github.dunwu.javacore.exception.ExceptionChainDemoMyException1: 出現(xiàn)
MyException1 at
io.github.dunwu.javacore.exception.ExceptionChainDemo.f1(ExceptionChainDemo.java:22)
at
io.github.dunwu.javacore.exception.ExceptionChainDemo.f2(ExceptionChainDemo.java:27)
… 1 more
擴(kuò)展閱讀:https://juejin.im/post/5b6d61e55188251b38129f9a#heading-10
這篇文章中對(duì)于異常鏈講解比較詳細(xì)。
6. 異常注意事項(xiàng)
6.1. finally 覆蓋異常
Java 異常處理中 finally 中的 return 會(huì)覆蓋 catch 代碼塊中的 return 語(yǔ)句和 throw 語(yǔ)句,所以 Java 不建議在 finally 中使用 return 語(yǔ)句。
此外 finally 中的 throw 語(yǔ)句也會(huì)覆蓋 catch 代碼塊中的 return 語(yǔ)句和 throw 語(yǔ)句。
示例:
public class FinallyOverrideExceptionDemo {static void f() throws Exception {try {throw new Exception("A");} catch (Exception e) {throw new Exception("B");} finally {throw new Exception("C");}}public static void main(String[] args) {try {f();} catch (Exception e) {System.out.println(e.getMessage());}} }輸出:C
6.2. 覆蓋拋出異常的方法
當(dāng)子類重寫父類帶有 throws 聲明的函數(shù)時(shí),其 throws 聲明的異常必須在父類異常的可控范圍內(nèi)——用于處理父類的 throws 方法的異常處理器,必須也適用于子類的這個(gè)帶 throws 方法 。這是為了支持多態(tài)。
示例:
public class ExceptionOverrideDemo {static class Father {public void start() throws IOException {throw new IOException();}}static class Son extends Father {@Overridepublic void start() throws SQLException {throw new SQLException();}}public static void main(String[] args) {Father obj1 = new Father();Father obj2 = new Son();try {obj1.start();obj2.start();} catch (IOException e) {e.printStackTrace();}} }上面的示例編譯時(shí)會(huì)報(bào)錯(cuò),原因在于:
因?yàn)?Son 類拋出異常的實(shí)質(zhì)是 SQLException,而 IOException 無(wú)法處理它。那么這里的 try catch 就不能處理 Son 中的異常了。多態(tài)就不能實(shí)現(xiàn)了。
6.3. 異常和線程
如果 Java 程序只有一個(gè)線程,那么沒(méi)有被任何代碼處理的異常會(huì)導(dǎo)致程序終止。如果 Java 程序是多線程的,那么沒(méi)有被任何代碼處理的異常僅僅會(huì)導(dǎo)致異常所在的線程結(jié)束。
7. 最佳實(shí)踐
對(duì)可恢復(fù)的情況使用檢查性異常(Exception),對(duì)編程錯(cuò)誤使用運(yùn)行時(shí)異常(RuntimeException)。
優(yōu)先使用 Java 標(biāo)準(zhǔn)的異常。
拋出與抽象相對(duì)應(yīng)的異常。
在細(xì)節(jié)消息中包含能捕獲失敗的信息。
盡可能減少 try 代碼塊的大小。
盡量縮小異常范圍。例如,如果明知嘗試捕獲的是一個(gè) ArithmeticException,就應(yīng)該 catch ArithmeticException,而不是 catch 范圍較大的 RuntimeException,甚至是 Exception。
盡量不要在 finally 塊拋出異常或者返回值。
不要忽略異常,一旦捕獲異常,就應(yīng)該處理,而非丟棄。
異常處理效率很低,所以不要用異常進(jìn)行業(yè)務(wù)邏輯處理。
各類異常必須要有單獨(dú)的日志記錄,將異常分級(jí),分類管理,因?yàn)橛械臅r(shí)候僅僅想給第三方運(yùn)維看到邏輯異常,而不是更細(xì)節(jié)的信息。
如何對(duì)異常進(jìn)行分類:
邏輯異常,這類異常用于描述業(yè)務(wù)無(wú)法按照預(yù)期的情況處理下去,屬于用戶制造的意外。
代碼錯(cuò)誤,這類異常用于描述開發(fā)的代碼錯(cuò)誤,例如 NPE,ILLARG,都屬于程序員制造的 BUG。
專有異常,多用于特定業(yè)務(wù)場(chǎng)景,用于描述指定作業(yè)出現(xiàn)意外情況無(wú)法預(yù)先處理。
原文鏈接:https://dunwu.github.io/javacore/basics/java-exception.html
總結(jié)
以上是生活随笔為你收集整理的【转载】一文彻底拿下Java异常的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: rabbitmq的启动命令和spring
- 下一篇: 【笔记】mybatis的sqlSessi