詳解Java的Exception異常機(jī)制
一、前言
在Java中,我們?cè)趫?zhí)行代碼的過程中難免會(huì)遇到錯(cuò)誤與Exception異常,可是我們一直都是錘頭Coding而忽略了學(xué)習(xí)Exception這個(gè)東西!我們只是知道在發(fā)生Exception的地方讓代碼自動(dòng)生成throw exception或者是使用try-catch括起來處理,那你了解Java的Exception嗎?今天就讓我們把一起來看看Java的Exception吧!
在Java中,我們的代碼再出現(xiàn)錯(cuò)誤的時(shí)候無非是兩種情況:一是Error,一是異常Exception。如果是Error,那么則說明出現(xiàn)了錯(cuò)誤,錯(cuò)誤出現(xiàn)在編譯器預(yù)編譯階段,錯(cuò)誤是可以預(yù)見的,而異常卻是無法預(yù)見的。Java中,無論是Error還是Exception都是Throwable的子類(見下圖)。這是個(gè)什么玩意兒,可能大部分的人都沒人聽說過吧…不過這并不重要…開始上圖…
Error與Exception的區(qū)別與相同點(diǎn):
- Error是嚴(yán)重的錯(cuò)誤,并非異常,錯(cuò)誤是無法通過編碼解決的。
- Exception是異常,異常則是可以通過編碼解決的。
Error與Exception的相同點(diǎn):
- Error與Exception類都是Throwable的子類
Exception類是所有異常的超類。
Java所有的異常在尋找父類的時(shí)候最終都能找到Exception類。
異常的分類
java.lang.Exception類是所有異常的超類,主要分為以下兩種:
RuntimeException-運(yùn)行時(shí)異常,也叫作非檢測(cè)性異常.
lOException和其它異常-其它異常,也叫作檢測(cè)性異常,所謂檢測(cè)性異常就是指在編譯階段都能被編譯器檢測(cè)出來的異常。
其中RuntimeException類的主要子類︰ArithmeticException類-算術(shù)異常,ArraylndexOutOfBoundsException類-數(shù)組下標(biāo)越界異常,NullPointerException-空指針異常,ClassCastException-類型轉(zhuǎn)換異常,NumberEormatException-數(shù)字格式異常注意:
當(dāng)程序執(zhí)行過程中發(fā)生異常但又沒有手動(dòng)處理時(shí),則由Java虛擬機(jī)采用默認(rèn)方式處理異常,而默認(rèn)處理方式就是︰打印異常的名稱、異常發(fā)生的原因、異常發(fā)生的位置以及終止程序。
二、關(guān)于RuntimeException
非檢測(cè)性異常
package com.sinsy.exception; public class ExceptionTest { public static void main(String[] args) { // 非檢測(cè)性異常 : Exception in thread "main" java.lang.ArithmeticException: / by zero jvm無法檢測(cè) System.out.println(5 / 0); //檢測(cè)型異常 Thread.sleep(3000); } }
RuntimeException的主要子類有:
- ArithmeticException–算數(shù)異常
- ArrayIndexOutOfBoundsException–數(shù)組下標(biāo)越界異常
- NullPointerException–空指針異常
- ClassCastException–類型轉(zhuǎn)換異常
- NumberFormatException–數(shù)字格式異常
下面通過代碼來對(duì)異常做出解釋:
為了讓所有的異常都能正常的顯示出來,我這里對(duì)每一種異常使用多線程來打印輸出:
package com.sinsy.exception; public class ExceptionTest { public static void main(String[] args) throws Exception { // 非檢測(cè)性異常 : Exception in thread "main" java.lang.ArithmeticException: / by zero jvm無法檢測(cè) // System.out.println(5 / 0); //檢測(cè)型異常 //Thread.sleep(3000); exceptionTest(); } public static void exceptionTest(){ new Thread(new Runnable() { @Override public void run() { //第一種 數(shù)組下標(biāo)越界異常 int arr[] = new int[5]; System.out.println(arr[5]); } }).start(); new Thread(new Runnable() { @Override public void run() { //算數(shù)異常 int a = 1; int b = 0; System.out.println(a/b); } }).start(); new Thread(new Runnable() { @Override public void run() { //空指針異常 String abc = null; System.out.println(abc.length()); } }).start(); new Thread(new Runnable() { @Override public void run() { //類型轉(zhuǎn)換異常 Exception exception = new Exception(); InterruptedException interruptedException =(InterruptedException) exception; } }).start(); new Thread(new Runnable() { @Override public void run() { //數(shù)字格式異常 String ad = "1234a"; System.out.println(Integer.parseInt(ad)); } }).start(); } }
運(yùn)行之后的結(jié)果為:很清晰的看見,每一個(gè)線程代表著一種異常。
通過編碼對(duì)各種異常進(jìn)行處理之后,結(jié)果為:
/** * 測(cè)試各種異常轉(zhuǎn)換機(jī)制的修復(fù) */ public static void exceptionModifyTest(){ new Thread(() -> { //第一種 數(shù)組下標(biāo)越界異常 int arr[] = new int[5]; if (arr.length>5){ System.out.println(arr[5]); } System.out.println("下標(biāo)越界異常已經(jīng)規(guī)避"); }).start(); new Thread(new Runnable() { @Override public void run() { //算數(shù)異常 int a = 1; int b = 0; if (0!=b){ System.out.println(a/b); } System.out.println("算術(shù)異常已經(jīng)規(guī)避"); } }).start(); new Thread(() -> { //空指針異常 String abc = null; if (null!=abc) { System.out.println(abc.length()); } System.out.println("空指針異常已經(jīng)規(guī)避"); }).start(); new Thread(() -> { //類型轉(zhuǎn)換異常 Exception exception = new Exception(); if (exception instanceof InterruptedException) { InterruptedException interruptedException = (InterruptedException) exception; } System.out.println("類型轉(zhuǎn)換異常已經(jīng)規(guī)避"); }).start(); new Thread(() -> { //數(shù)字格式異常 String ad = "1234a"; //字符串類型的數(shù)據(jù)做篩選一般選擇正則表達(dá)式做第一選擇 if (ad.matches("\\d+")){ System.out.println(Integer.parseInt(ad)); } System.out.println("數(shù)字格式異常已避免"); }).start(); }
運(yùn)行結(jié)果為:
三、異常的避免
異常的避免其實(shí)上文已經(jīng)凸顯出來了結(jié)局方案,就是在可能出現(xiàn)異常的地方進(jìn)行if判斷處理,提前預(yù)判異常,然后對(duì)可能出現(xiàn)的異常跳過處理。
異常的避免(使用If-else)可能會(huì)導(dǎo)致大量的代碼冗余,導(dǎo)致代碼的沉積度過大,變得臃腫,可讀性比較差。
四、異常的捕獲
異常的捕獲使用try{}catch{}來進(jìn)行捕獲
捕獲不了的我們對(duì)其進(jìn)行拋出
五、異常的拋出(異常的轉(zhuǎn)移)
異常拋出拋給誰呢?當(dāng)然是異常的捕獲者。
異常拋出的時(shí)候需要注意一點(diǎn),拋出的異常之間有父子類繼承關(guān)系的,如果跑的異常包含多個(gè),那么我們可以選擇拋出異常最大的父類,直接可以拋出所有異常,這樣節(jié)省了代碼,但同時(shí)也對(duì)代碼的可讀性造成了一定的影響,讓修改者無法直接得知此代碼在使用過程中會(huì)出現(xiàn)什么樣的異常。
基本概念
在某些特殊情況下有些異常不能處理或者不便于處理時(shí),就可以將該異常轉(zhuǎn)秘給該方法的調(diào)用者,這種方法就叫異常的拋出,本質(zhì)上其實(shí)是異常的轉(zhuǎn)移。方法執(zhí)行時(shí)出現(xiàn)異常,則底層生成一個(gè)異常類對(duì)象拋出,此時(shí)異常代碼后續(xù)的代碼就不再執(zhí)行。
語法格式
訪問權(quán)限返回值類型方法名稱(形參列表) throws異常類型1,異常類型2…{方法體;}如︰
public void show() throws lOExceptiont
方法重寫的原則
a.要求方法名相同、參數(shù)列表相同以及返回值類型相同,從jdk1.5開始支持返回子類類型;b.要求方法的訪問權(quán)限不能變小,可以相同或者變大;
c.要求方法不能拋出更大的異常;
注意
子類重寫的方法不能拋出更大的異常、不能拋出平級(jí)不一樣的異常,但可以拋出一樣的異常、更小的異常以及不拋出異常。一定注意?。。。。。。。。?!若父類中被重寫的方法沒有拋出異常時(shí),則子類中重寫的方法只能進(jìn)行異常的捕獲。不建議在Main方法中拋出異常,JVM需要執(zhí)行的任務(wù)有很多,如果此時(shí)將大量的異常工作交給他來做,那么會(huì)影響JVMd額執(zhí)行效率
異常的常規(guī)處理
對(duì)于常見的異常,通常對(duì)不同類型的異常有著不同的處理方法.
(1)如果父類方法拋出了異常,子類不能拋出比父類更大的異常,只能拋出與父類所拋異常類子類的異常,特殊情況可不拋出任何異常。
- 父類拋出InterruptedException與IOException,子類只拋出InterruptedException,不拋出IOException
/** * 父類 */ package com.sinsy.test; import java.io.FileInputStream; import java.io.IOException; public class ThrowExceptionFather { public void show() throws IOException, InterruptedException { Thread.sleep(100); System.out.println("hehehe"); FileInputStream fileInputStream =new FileInputStream(""); fileInputStream.close(); } } /** * 子類 */ package com.sinsy.test; public class ThrowExceptionTest extends ThrowExceptionFather{ @Override public void show() throws InterruptedException { Thread.sleep(20); } }
- 父類的原始方法只拋出IOExeption,而子類在重寫該方法的時(shí)候拋出父類中沒有的異常,并且該異常與父類所拋異常并非同屬某一類的子類,也就是說子類所拋出的異常與父類所拋出的異常并不是同一級(jí)。
/** * 父類 */ package com.sinsy.test; import java.io.FileInputStream; import java.io.IOException; public class ThrowExceptionFather { public void show() throws IOException { FileInputStream fileInputStream =new FileInputStream(""); fileInputStream.close(); } } /** * 子類 */ package com.sinsy.test; public class ThrowExceptionTest extends ThrowExceptionFather{ @Override public void show() throws InterruptedException { Thread.sleep(20); } }
此時(shí)代碼報(bào)錯(cuò),證明子類無法拋出與父類非同級(jí)關(guān)系的異常:
- 父類只拋出一個(gè)異常,子類拋出與父類所拋異常同級(jí)但不同的異常:如下代碼:子類拋出了一個(gè)與父類方法中同級(jí)別的異常但父類并未拋出該異常,此時(shí)代碼報(bào)錯(cuò):證明子類不可拋出與父類同級(jí)別的異常:(ClassNotLoadedException與IOException屬于同一級(jí)別)
/** * 父類 */ package com.sinsy.test; import com.sun.jdi.ClassNotLoadedException; import java.io.FileInputStream; import java.io.IOException; public class ThrowExceptionFather { public void show() throws IOException { FileInputStream fileInputStream =new FileInputStream(""); fileInputStream.close(); } } /** * 子類 */ package com.sinsy.test; import com.sun.jdi.ClassNotLoadedException; public class ThrowExceptionTest extends ThrowExceptionFather{ @Override public void show() throws ClassNotLoadedException { } }
但是子類可以拋出一個(gè)是父類所拋異常類的子類的異常:
通過對(duì)比IOException的簡單繼承關(guān)系我們可以選擇一個(gè)IOException的子類,讓繼承了ThrowExceptionFather的子類在方法重寫的時(shí)候去拋出該異常,如下代碼:
package com.sinsy.test; import com.sun.jdi.ClassNotLoadedException; import java.nio.file.FileSystemException; public class ThrowExceptionTest extends ThrowExceptionFather{ @Override public void show() throws FileSystemException { } }
此時(shí)未報(bào)錯(cuò):
證明,子類可以拋出父類所拋異常類子類的異常。
因此,總結(jié)一句話就是:子類繼承父類后,在重寫父類方法的時(shí)候,如果父類中原有方法拋出了異常,那么子類不能拋出比父類所拋異常大的異常,只能拋出屬于父類所拋異常類的子類的異常。
(2)若一個(gè)方法內(nèi)部以遞進(jìn)式的方法分別調(diào)用了好幾個(gè)其他的方法,則建議這些方法將異常逐層拋出,然后再統(tǒng)一處理。如下代碼所示。
package com.sinsy.test; public class DigonThrow { public static void yu() { try { yu1(); } catch (Exception e) { e.printStackTrace(); } } public static void yu1() throws Exception { yu2(); } public static void yu2() throws Exception { yu3(); } public static void yu3() throws Exception { yu4(); } public static void yu4() throws Exception { yu5(); } public static void yu5()throws Exception{ System.out.println(1); } public static void main(String[] args){ yu(); } }
將異常拋至最頂層的時(shí)候,此時(shí)做最后的 try-catch 處理。
(3)如果父類中被重寫的方法沒有拋出異常的時(shí)候,則子類中重寫的方法只能進(jìn)行異常的捕獲處理。
//父類代碼 package com.sinsy.test; public class ThrowExceptionFather { public void show(){ } } //子類代碼 package com.sinsy.test; import java.io.FileInputStream; import java.io.IOException; public class ThrowExceptionTest extends ThrowExceptionFather{ @Override public void show() { FileInputStream fileInputStream =new FileInputStream(""); fileInputStream.close(); } }
若此時(shí)子類拋出異常,則會(huì)出現(xiàn)以下警示:
Method ‘show' is inherited.Do you want to add exceptions to method signatures in the whole method hierarchy?意思就是在說,方法‘Show'是繼承的。要在整個(gè)方法層次結(jié)構(gòu)中向方法簽名添加異常嗎?如果添加了之后,則父類中過就會(huì)拋出此異常!
六、自定義異常
基本概念
當(dāng)需要在程序中表達(dá)年齡不合理的情況時(shí),而Java官方又沒有提供這種針對(duì)性的異常,此時(shí)就需要程序員自定義異常加以描述。
實(shí)現(xiàn)流程
a.自定義xxxException異常類繼承Exception類或者其子類。
b.提供兩個(gè)版本的構(gòu)造方法,一個(gè)是在·無參構(gòu)造方法,另外一個(gè)是字符電作為參數(shù)的構(gòu)造方法。
異常的產(chǎn)生
throw new異常類型(實(shí)參);如∶throw new AgeException("年齡不合理!!! ");Java采用的異常處理機(jī)制,是將異常處理的程序代碼集中在一起,與正常的程序代碼分開**,使得程序簡潔、優(yōu)雅,并易于維護(hù)。**
自定義Exception異常類
要想實(shí)現(xiàn)自定義Exception異常類,我們需要寫一個(gè)Exception的異常類去繼承Exception類,實(shí)現(xiàn)無參構(gòu)造方法以及有參數(shù)構(gòu)造方法。
package com.sinsy.exception; public class AgeException extends Exception{ static final long serialVersionUID = -3387516993124229948L; /** * 無參數(shù)構(gòu)造方法 */ public AgeException() { } /** * 有參構(gòu)造方法 * @param message */ public AgeException(String message) { super(message); } }
- 自定義Exception異常類的使用:
(1)首先確定應(yīng)用場(chǎng)景為:用戶個(gè)人信息的封裝中,對(duì)成員屬性的封裝的時(shí)候?qū)Τ蓡T屬性的設(shè)置出現(xiàn)異常:
package com.sinsy.bean; import com.sinsy.exception.AgeException; import java.util.Objects; public class Person { private String name; private String sex; private int age; public Person() { } public Person(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) throws AgeException { if (age<0){ throw new AgeException("年齡設(shè)置錯(cuò)誤哦!請(qǐng)稍后重試!"); }else{ this.age = age; } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name) && Objects.equals(sex, person.sex); } @Override public int hashCode() { return Objects.hash(name, sex, age); } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", sex='" + sex + '\'' + ", age=" + age + '}'; } }
(2)確定引用場(chǎng)景之后,我們創(chuàng)建Test測(cè)試方法對(duì)目標(biāo)用例進(jìn)行簡單測(cè)試:
package com.sinsy.test; import com.sinsy.bean.Person; import com.sinsy.exception.AgeException; public class PersonException { public static void main(String[] args) { Person person = new Person("二狗子"); try { person.setAge(-12); }catch (AgeException ageException){ ageException.printStackTrace(); } System.out.println("執(zhí)行完畢"); } }
測(cè)試結(jié)果如下:
這表明,AgeException的異常類使用正確,正確的打印出來了錯(cuò)誤提示信息,這里的錯(cuò)誤提示信息是自己自定義的,也就是你在調(diào)用AgeException類的時(shí)候自己輸入的異常提示信息。這就是調(diào)用了AgeExceptiuon類的默認(rèn)含參數(shù)的構(gòu)造方法。
注意含有異常處理的代碼執(zhí)行順序:
在遇到異常的時(shí)候我們需要根據(jù)異常出現(xiàn)的具體應(yīng)用場(chǎng)景作相應(yīng)的處理,如果是重寫父類的繼承下來的方法,我們?cè)趯?duì)方法進(jìn)行重寫的時(shí)候,我們需要注意,如果父類的原始方法并沒有拋出異常,在子類中重寫父類的方法是不需要拋出異常的,否則則會(huì)報(bào)錯(cuò),而在處理異常的時(shí)候,我們更需要關(guān)注的是異常出現(xiàn)的位置以及時(shí)機(jī)。如果異常出現(xiàn)在可預(yù)測(cè)區(qū)域,則主動(dòng)應(yīng)該拋出對(duì)應(yīng)可能會(huì)出現(xiàn)的異常,讓代碼安全的執(zhí)行,不產(chǎn)生額外的錯(cuò)誤。其二,在處理異常的時(shí)候,如果是遞進(jìn)式拋出異常,在最頂層的時(shí)候一定要try-catch處理異常,不可再向上拋出,如果拋給虛擬機(jī)來處理,那么則會(huì)響應(yīng)JVM的執(zhí)行效率。其三,當(dāng)處理異常的時(shí)候,如果我們遇到與異常耦合度不高的業(yè)務(wù)需要執(zhí)行的時(shí)候,我們可以在finally中編寫與異常無耦合度的代碼,確保必要的有業(yè)務(wù)能正常執(zhí)行。
處理異常的時(shí)候由于finally與try-catch的關(guān)系,有時(shí)候會(huì)打亂原有代碼的執(zhí)行順序,也會(huì)間接影響業(yè)務(wù)的執(zhí)行:比如以下處理方法的不同導(dǎo)致在業(yè)務(wù)最后的結(jié)果完全不一致。代碼如下
package com.sinsy.test; import com.sinsy.bean.Person; import com.sinsy.exception.AgeException; public class PersonException { public static void main(String[] args) { Person person = new Person("二狗子"); //注意我這里的處理異常的方式方法: 是在最頂層拿到異常之后直接當(dāng)場(chǎng)做的處理, try { person.setAge(-12); }catch (AgeException ageException){ //這里就是處理 直接將異常打印輸出 ageException.printStackTrace(); } System.out.println("執(zhí)行完畢"); } }
處理之后的結(jié)果為:
**而如果我們將代碼做一個(gè)簡單的修改:**在設(shè)置年齡的2時(shí)候我們不對(duì)錯(cuò)誤年齡進(jìn)行拋出異常處理而是直接就地處理。則會(huì)怎么樣呢?為了讓效果更加明顯,我們將SetAge的方法修改完之后我們?cè)倮^續(xù)將測(cè)試方法中測(cè)試用例做一個(gè)簡單的更改:
public void setAge(int age) { if (age<0){ try { throw new AgeException("年齡設(shè)置錯(cuò)誤哦!請(qǐng)稍后重試!"); }catch (AgeException ageException){ ageException.printStackTrace(); } }else{ this.age = age; } } //測(cè)試方法: package com.sinsy.test; import com.sinsy.bean.Person; import com.sinsy.exception.AgeException; public class PersonException { public static void main(String[] args) { Person person = new Person("二狗子","男"); //注意我這里的處理異常的方式方法: 是在最頂層拿到異常之后直接當(dāng)場(chǎng)做的處理, person.setAge(-12); System.out.println(person); } }
最后的處理結(jié)果:
我們發(fā)現(xiàn),測(cè)試方法執(zhí)行之后,打印輸出了Person對(duì)象person。
可見,在設(shè)置年齡的時(shí)候就地處理,不會(huì)影響后續(xù)代碼的執(zhí)行,就地處理后,我們捕獲到異常,怎么處理異常取決于我們對(duì)異常的動(dòng)作,我們可以選擇將捕獲到的異常進(jìn)行打印亦或是將異常轉(zhuǎn)交到其他處理異常的業(yè)務(wù)中去處理,這樣的做了就地處理不會(huì)影響我們后續(xù)的代碼執(zhí)行。
好了,以上就是今天對(duì)Java異常的介紹與使用以及教學(xué),下一篇我們就來研究一下Java中的異常的底層是怎么實(shí)現(xiàn)的,Exception在Java中是一種什么樣的存在?設(shè)計(jì)Exception與Error的目的在JVM處理字節(jié)碼文件指令集的過程中能起到什么樣的效果?
說到這里,如果你有一點(diǎn)基礎(chǔ)的話,相信肯定能看到我對(duì)作用(設(shè)計(jì)Exception與Error的目的在JVM處理字節(jié)碼文件指令集的過程中能起到的作用)的一些簡單了解,沒事,不夠清晰,我們還有第二篇博客詳解Exception與Error。
到此這篇關(guān)于詳解Java的Exception異常機(jī)制的文章就介紹到這了,更多相關(guān)Java Exception異常內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA驗(yàn)證身份證號(hào)碼有效性的實(shí)例代碼
很多項(xiàng)目業(yè)務(wù)都會(huì)設(shè)計(jì)到人員信息,那么身份證號(hào)就是必不可少的校驗(yàn)項(xiàng),下面這篇文章主要給大家介紹了關(guān)于JAVA驗(yàn)證身份證號(hào)碼有效性的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08linux下idea、pycharm等輸入中文拼音時(shí)滿3個(gè)字母后無法繼續(xù)拼音輸入的問題
這篇文章主要介紹了linux下idea、pycharm等輸入中文拼音時(shí)滿3個(gè)字母后無法繼續(xù)拼音輸入的問題,本文通過圖文并茂的形式給大家分享解決方法,需要的朋友可以參考下2021-04-04MyBatis學(xué)習(xí)教程(三)-MyBatis配置優(yōu)化
這篇文章主要介紹了MyBatis學(xué)習(xí)教程(三)-MyBatis配置優(yōu)化的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-05-05Java中LambdaQueryWrapper設(shè)置自定義排序代碼示例
這篇文章主要給大家介紹了關(guān)于Java中LambdaQueryWrapper設(shè)置自定義排序的相關(guān)資料,lambdaquerywrapper是MyBatis-Plus框架中的一個(gè)查詢條件構(gòu)造器,它可以用于構(gòu)建自定義的查詢條件,需要的朋友可以參考下2023-12-12Java如何實(shí)現(xiàn)http接口參數(shù)和返回值加密
這篇文章主要介紹了Java如何實(shí)現(xiàn)http接口參數(shù)和返回值加密問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11Java?RabbitMQ的持久化和發(fā)布確認(rèn)詳解
這篇文章主要為大家詳細(xì)介紹了RabbitMQ的持久化和發(fā)布確認(rèn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03