Java中異常Exception和捕獲以及自定義異常詳解
1. 異常概述
1.1 什么是程序的異常
在使用計(jì)算機(jī)語言進(jìn)行項(xiàng)目開發(fā)的過程中,即使程序員把代碼寫得盡善盡美,在系統(tǒng)的運(yùn)行過程中仍然會(huì)遇到一些問題,因?yàn)楹芏鄦栴}不是靠代碼能夠避免的。
異常 :指的是程序在執(zhí)行過程中,出現(xiàn)的非正常情況,如果不處理最終會(huì)導(dǎo)致JVM的非正常停止。
異常指的并不是語法錯(cuò)誤和邏輯錯(cuò)誤。語法錯(cuò)了,編譯不通過,不會(huì)產(chǎn)生字節(jié)碼文件,根本不能運(yùn)行。代碼邏輯錯(cuò)誤,只是沒有得到想要的結(jié)果,例如:求a與b的和,你寫成了a-b。
1.2 異常的拋出機(jī)制
Java中把不同的異常用不同的類表示,一旦發(fā)生某種異常,就創(chuàng)建該異常類型的對(duì)象,并且拋出(throw
)。然后程序員可以捕獲(catch
)到這個(gè)異常對(duì)象,并處理;如果沒有捕獲(catch)這個(gè)異常對(duì)象,那么這個(gè)異常對(duì)象將會(huì)導(dǎo)致程序終止。
模擬程序會(huì)產(chǎn)生一個(gè)數(shù)組角標(biāo)越界異常(ArrayIndexOfBoundsException):
public class ArrayTools { // 對(duì)給定的數(shù)組通過給定的角標(biāo)獲取元素。 public static int getElement(int[] arr, int index) { int element = arr[index]; return element; } } //測試類 public class ExceptionDemo { public static void main(String[] args) { int[] arr = { 34, 12, 67 }; intnum = ArrayTools.getElement(arr, 4) System.out.println("num=" + num); System.out.println("over"); } }
上述程序執(zhí)行過程圖解:
1.3 如何對(duì)待異常
對(duì)于程序出現(xiàn)的異常,一般有兩種解決方法:一是遇到錯(cuò)誤就終止程序的運(yùn)行。另一種方法是程序員在編寫程序時(shí),就充分考慮到各種可能發(fā)生的異常和錯(cuò)誤,極力預(yù)防和避免。實(shí)在無法避免的,要編寫相應(yīng)的代碼進(jìn)行異常的檢測、以及異常的處理,保證代碼的健壯性。
2. Java異常體系
2.1 Throwable
Throwable中的常用方法:
- public void printStackTrace():打印異常的詳細(xì)信息。
包含了異常的類型、異常的原因、異常出現(xiàn)的位置、在開發(fā)和調(diào)試階段都得使用printStackTrace
。
- public String getMessage():獲取發(fā)生異常的原因。
2.2 Error 和 Exception
Throwable
可分為兩類:Error
和Exception
。分別對(duì)應(yīng)著java.lang.Error
與java.lang.Exception
兩個(gè)類。
Error:Java虛擬機(jī)無法解決的嚴(yán)重問題。如:JVM系統(tǒng)內(nèi)部錯(cuò)誤、資源耗盡等嚴(yán)重情況。一般不編寫針對(duì)性的代碼進(jìn)行處理。
- 例如:StackOverflowError(棧內(nèi)存溢出)和OutOfMemoryError(堆內(nèi)存溢出,簡稱OOM)。
Exception: 其它因編程錯(cuò)誤或偶然的外在因素導(dǎo)致的一般性問題,需要使用針對(duì)性的代碼進(jìn)行處理,使程序繼續(xù)運(yùn)行。否則一旦發(fā)生異常,程序也會(huì)掛掉。
- 空指針訪問
- 試圖讀取不存在的文件
- 網(wǎng)絡(luò)連接中斷
- 數(shù)組角標(biāo)越界
- …
說明:
- 無論是Error還是Exception,還有很多子類,異常的類型非常豐富。當(dāng)代碼運(yùn)行出現(xiàn)異常時(shí),特別是我們不熟悉的異常時(shí),不要緊張,把異常的類名,找到對(duì)應(yīng)API中去了解是什么類型的異常。
2.3 編譯時(shí)異常和運(yùn)行時(shí)異常
Java程序的執(zhí)行分為編譯時(shí)過程和運(yùn)行時(shí)過程。有的錯(cuò)誤只有在運(yùn)行時(shí)才會(huì)發(fā)生。
根據(jù)異常可能出現(xiàn)的階段,可以將異常分為:
- 編譯時(shí)期異常(即checked異常、受檢異常):在代碼編譯階段,編譯器就能明確警示當(dāng)前代碼可能發(fā)生(不是一定發(fā)生)xx異常,并明確督促程序員提前編寫處理它的代碼。如果程序員沒有編寫對(duì)應(yīng)的異常處理代碼,則編譯器就會(huì)直接判定編譯失敗,從而不能生成字節(jié)碼文件。通常,這類異常的發(fā)生不是由程序員的代碼引起的,或者不是靠加簡單判斷就可以避免的,例如:FileNotFoundException(文件找不到異常)。
- 運(yùn)行時(shí)期異常(即runtime異常、unchecked異常、非受檢異常):在代碼編譯階段,編譯器完全不做任何檢查,無論該異常是否會(huì)發(fā)生,編譯器都不給出任何提示。只有等代碼運(yùn)行起來并確實(shí)發(fā)生了xx異常,它才能被發(fā)現(xiàn)。通常,這類異常是由程序員的代碼編寫不當(dāng)引起的,只要稍加判斷,或者細(xì)心檢查就可以避免。
java.lang.RuntimeException
:類及它的子類都是運(yùn)行時(shí)異常。比如:ArrayIndexOutOfBoundsException
:數(shù)組下標(biāo)越界異常,ClassCastException
類型轉(zhuǎn)換異常。
3. 常見的錯(cuò)誤和異常
3.1 Error
最常見的就是VirtualMachineError
,它有兩個(gè)經(jīng)典的子類:StackOverflowError
、OutOfMemoryError
。
@Test public void test01(){ //StackOverflowError recursion(); } public void recursion(){ //遞歸方法 recursion(); }
@Test public void test02(){ //OutOfMemoryError //方式一: int[] arr = new int[Integer.MAX_VALUE]; }
3.2 運(yùn)行時(shí)異常
@Test public void test05(){ int a = 1; int b = 0; //ArithmeticException System.out.println(a/b); }
3.3 編譯時(shí)異常
@Test public void test06() { Thread.sleep(1000);//休眠1秒 InterruptedException }
4. 異常的處理
4.1 異常處理概述
在編寫程序時(shí),經(jīng)常要在可能出現(xiàn)錯(cuò)誤的地方加上檢測的代碼,如進(jìn)行x/y運(yùn)算時(shí),要檢測分母為0,數(shù)據(jù)為空,輸入的不是數(shù)據(jù)而是字符等。過多的if-else分支會(huì)導(dǎo)致程序的代碼加長、臃腫,可讀性差,程序員需要花很大的精力“堵漏洞”。因此采用異常處理機(jī)制。
Java異常處理:
Java采用的異常處理機(jī)制,是將異常處理的程序代碼集中在一起,與正常的程序代碼分開,使得程序簡潔、優(yōu)雅,并易于維護(hù)。
Java異常處理的方式:
- 方式一: try-catch-finally
- 方式二: throws + 異常類型
4.2 捕獲異常(try-catch-finally)
Java提供了異常處理的抓拋模型。
- Java程序的執(zhí)行過程中如出現(xiàn)異常,會(huì)生成一個(gè)異常類對(duì)象,該異常對(duì)象將被提交給Java運(yùn)行時(shí)系統(tǒng),這個(gè)過程稱為拋出(throw)異常。
- 如果一個(gè)方法內(nèi)拋出異常,該異常對(duì)象會(huì)被拋給調(diào)用者方法中處理。如果異常沒有在調(diào)用者方法中處理,它繼續(xù)被拋給這個(gè)調(diào)用方法的上層方法。這個(gè)過程將一直繼續(xù)下去,直到異常被處理。這一過程稱為捕獲(catch)異常。
- 如果一個(gè)異常回到main()方法,并且main()也不處理,則程序運(yùn)行終止。
4.2.1 try-catch-finally基本格式
try{ ...... //可能產(chǎn)生異常的代碼 } catch( 異常類型1 e ){ ...... //當(dāng)產(chǎn)生異常類型1型異常時(shí)的處置措施 } catch( 異常類型2 e ){ ...... //當(dāng)產(chǎn)生異常類型2型異常時(shí)的處置措施 } finally{ ...... //無論是否發(fā)生異常,都無條件執(zhí)行的語句 }
1、整體執(zhí)行過程:
當(dāng)某段代碼可能發(fā)生異常,不管這個(gè)異常是編譯時(shí)異常(受檢異常)還是運(yùn)行時(shí)異常(非受檢異常),我們都可以使用try塊將它括起來,并在try
塊下面編寫catch
分支嘗試捕獲對(duì)應(yīng)的異常對(duì)象。
- 如果在程序運(yùn)行時(shí),try塊中的代碼沒有發(fā)生異常,那么catch所有的分支都不執(zhí)行。
- 如果在程序運(yùn)行時(shí),try塊中的代碼發(fā)生了異常,根據(jù)異常對(duì)象的類型,將從上到下選擇第一個(gè)匹配的catch分支執(zhí)行。此時(shí)try中發(fā)生異常的語句下面的代碼將不執(zhí)行,而整個(gè)try…catch之后的代碼可以繼續(xù)運(yùn)行。
- 如果在程序運(yùn)行時(shí),try塊中的代碼發(fā)生了異常,但是所有catch分支都無法匹配(捕獲)這個(gè)異常,那么JVM將會(huì)終止當(dāng)前方法的執(zhí)行,并把異常對(duì)象“拋”給調(diào)用者。如果調(diào)用者不處理,程序就掛了。
2、try:
捕獲異常的第一步是用try{…}
語句塊選定捕獲異常的范圍,將可能出現(xiàn)異常的業(yè)務(wù)邏輯代碼放在try語句塊中。
3、catch (Exceptiontype e)
- catch分支,分為兩個(gè)部分,catch()中編寫異常類型和異常參數(shù)名,{}中編寫如果發(fā)生了這個(gè)異常,要做什么處理的代碼。
- 如果明確知道產(chǎn)生的是何種異常,可以用該異常類作為catch的參數(shù);也可以用其父類作為catch的參數(shù)。
- 比如:可以用ArithmeticException類作為參數(shù)的地方,就可以用RuntimeException類作為參數(shù),或者用所有異常的父類Exception類作為參數(shù)。但不能是與ArithmeticException類無關(guān)的異常,如NullPointerException(catch中的語句將不會(huì)執(zhí)行)。
- 每個(gè)try語句塊可以伴隨一個(gè)或多個(gè)catch語句,用于處理可能產(chǎn)生的不同類型的異常對(duì)象。
- 如果有多個(gè)catch分支,并且多個(gè)異常類型有父子類關(guān)系,必須保證小的子異常類型在上,大的父異常類型在下。否則,報(bào)錯(cuò)。
- catch中常用異常處理的方式
- public String getMessage():獲取異常的描述信息,返回字符串
- public void printStackTrace():打印異常的跟蹤棧信息并輸出到控制臺(tái)。包含了異常的類型、異常的原因、還包括異常出現(xiàn)的位置,在開發(fā)和調(diào)試階段,都得使用printStackTrace()。
4.2.2 finally使用及舉例
- 因?yàn)楫惓?huì)引發(fā)程序跳轉(zhuǎn),從而會(huì)導(dǎo)致有些語句執(zhí)行不到。而程序中有一些特定的代碼無論異常是否發(fā)生,都需要執(zhí)行。例如,數(shù)據(jù)庫連接、輸入流輸出流、Socket連接、Lock鎖的關(guān)閉等,這樣的代碼通常就會(huì)放到finally塊中。所以,我們通常將一定要被執(zhí)行的代碼聲明在finally中。
- 唯一的例外,使用 System.exit(0) 來終止當(dāng)前正在運(yùn)行的 Java 虛擬機(jī)。
- 不論在try代碼塊中是否發(fā)生了異常事件,catch語句是否執(zhí)行,catch語句是否有異常,catch語句中是否有return,finally塊中的語句都會(huì)被執(zhí)行。
- finally語句和catch語句是可選的,但finally不能單獨(dú)使用。
4.3 聲明拋出異常類型(throws)
如果在編寫方法體的代碼時(shí),某句代碼可能發(fā)生某個(gè)編譯時(shí)異常,不處理編譯不通過,但是在當(dāng)前方法體中可能不適合處理或無法給出合理的處理方式,則此方法應(yīng)顯示地聲明拋出異常,表明該方法將不對(duì)這些異常進(jìn)行處理,而由該方法的調(diào)用者負(fù)責(zé)處理。
具體方式: 在方法聲明中用throws語句可以聲明拋出異常的列表,throws后面的異常類型可以是方法中產(chǎn)生的異常類型,也可以是它的父類。
4.3.1 throws基本格式
聲明異常格式:
修飾符 返回值類型 方法名(參數(shù)) throws 異常類名1,異常類名2…{ }
在throws后面可以寫多個(gè)異常類型,用逗號(hào)隔開。
public void readFile(String file) throws FileNotFoundException,IOException { ... // 讀文件的操作可能產(chǎn)生FileNotFoundException或IOException類型的異常 FileInputStream fis = new FileInputStream(file); //... }
throws
后面也可以寫運(yùn)行時(shí)異常類型,只是運(yùn)行時(shí)異常類型,寫或不寫對(duì)于編譯器和程序執(zhí)行來說都沒有任何區(qū)別。如果寫了,唯一的區(qū)別就是調(diào)用者調(diào)用該方法后,使用try…catch結(jié)構(gòu)時(shí),IDEA可以獲得更多的信息,需要添加哪種catch
分支。
4.3.2 方法重寫中throws的要求
方法重寫時(shí):
(1)方法名必須相同
(2)形參列表必須相同
(3)返回值類型
- 基本數(shù)據(jù)類型和
void
:必須相同 - 引用數(shù)據(jù)類型:<=
(4)權(quán)限修飾符:>=,而且要求父類被重寫方法在子類中是可見的
(5)不能是static
,final
修飾的方法
對(duì)于throws異常列表要求:
- 如果父類被重寫方法的方法簽名后面沒有 “throws 編譯時(shí)異常類型”,那么重寫方法時(shí),方法簽名后面也不能出現(xiàn)“throws 編譯時(shí)異常類型”。
- 如果父類被重寫方法的方法簽名后面有 “throws 編譯時(shí)異常類型”,那么重寫方法時(shí),throws的編譯時(shí)異常類型必須 <= 被重寫方法throws的編譯時(shí)異常類型,或者不throws編譯時(shí)異常。
- 方法重寫,對(duì)于“throws 運(yùn)行時(shí)異常類型”沒有要求。
4.4 兩種異常處理方式的選擇
對(duì)于異常,使用相應(yīng)的處理方式。此時(shí)的異常,主要指的是編譯時(shí)異常
- 如果程序代碼中,涉及到資源的調(diào)用(流、數(shù)據(jù)庫連接、網(wǎng)絡(luò)連接等),則必須考慮使用try-catch-finally來處理,保證不出現(xiàn)內(nèi)存泄漏。
- 如果父類被重寫的方法沒有throws異常類型,則子類重寫的方法中如果出現(xiàn)異常,只能考慮使用try-catch-finally進(jìn)行處理,不能throws。
- 開發(fā)中,方法a中依次調(diào)用了方法b,c,d等方法,方法b,c,d之間是遞進(jìn)關(guān)系。此時(shí),如果方法b,c,d中有異常,我們通常選擇使用throws,而方法a中通常選擇使用try-catch-finally。
5. 手動(dòng)拋出異常對(duì)象:throw
Java 中異常對(duì)象的生成有兩種方式:
- 由虛擬機(jī)自動(dòng)生成:程序運(yùn)行過程中,虛擬機(jī)檢測到程序發(fā)生了問題,那么針對(duì)當(dāng)前代碼,就會(huì)在后臺(tái)自動(dòng)創(chuàng)建一個(gè)對(duì)應(yīng)異常類的實(shí)例對(duì)象并拋出。
- 由開發(fā)人員手動(dòng)創(chuàng)建:new 異常類型([實(shí)參列表]);,如果創(chuàng)建好的異常對(duì)象不拋出對(duì)程序沒有任何影響,和創(chuàng)建一個(gè)普通對(duì)象一樣,但是一旦throw拋出,就會(huì)對(duì)程序運(yùn)行產(chǎn)生影響了。
5.1 使用格式
throw new 異常類名(參數(shù));
throw語句拋出的異常對(duì)象,和JVM自動(dòng)創(chuàng)建和拋出的異常對(duì)象一樣。
- 如果是編譯時(shí)異常類型的對(duì)象,同樣需要使用throws或者try…catch處理,否則編譯不通過。
- 如果是運(yùn)行時(shí)異常類型的對(duì)象,編譯器不提示。
- 可以拋出的異常必須是
Throwable
或其子類的實(shí)例。
5.2 使用注意點(diǎn)
無論是編譯時(shí)異常類型的對(duì)象,還是運(yùn)行時(shí)異常類型的對(duì)象,如果沒有被try..catch
合理的處理,都會(huì)導(dǎo)致程序崩潰。
throw語句會(huì)導(dǎo)致程序執(zhí)行流程被改變,throw語句是明確拋出一個(gè)異常對(duì)象,因此它下面的代碼將不會(huì)執(zhí)行。
如果當(dāng)前方法沒有try…catch處理這個(gè)異常對(duì)象,throw語句就會(huì)代替return語句提前終止當(dāng)前方法的執(zhí)行,并返回一個(gè)異常對(duì)象給調(diào)用者。
6. 自定義異常
6.1 為什么需要自定義異常類
Java中不同的異常類,分別表示著某一種具體的異常情況。那么在開發(fā)中總是有些異常情況是核心類庫中沒有定義好的,此時(shí)我們需要根據(jù)自己業(yè)務(wù)的異常情況來定義和業(yè)務(wù)相關(guān)的異常類。
6.2 如何自定義異常類
(1)要繼承一個(gè)異常類型
自定義一個(gè)編譯時(shí)異常類型:自定義類繼承java.lang.Exception。
自定義一個(gè)運(yùn)行時(shí)異常類型:自定義類繼承java.lang.RuntimeException。
(2)建議提供至少兩個(gè)構(gòu)造器,一個(gè)是無參構(gòu)造,一個(gè)是(String message)構(gòu)造器。
(3)自定義異常需要提供serialVersionUID
6.3 注意點(diǎn)
- 自定義的異常只能通過throw拋出。
- 自定義異常最重要的是異常類的名字和message屬性。當(dāng)異常出現(xiàn)時(shí),可以根據(jù)名字判斷異常類型。比如:TeamException(“成員已滿,無法添加”);
- 自定義異常對(duì)象只能手動(dòng)拋出。拋出后由try…catch處理,也可以甩鍋throws給調(diào)用者處理。
示例:
//自定義異常: public class NotTriangleException extends Exception{ static final long serialVersionUID = 13465653435L; public NotTriangleException() { } public NotTriangleException(String message) { super(message); } } //手動(dòng)拋出異常對(duì)象 throw new NotTriangleException("輸入數(shù)據(jù)不正確");
7. 總結(jié)
異常處理5個(gè)關(guān)鍵字:
總結(jié)
到此這篇關(guān)于Java中異常Exception和捕獲以及自定義異常的文章就介紹到這了,更多相關(guān)Java異常Exception和捕獲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java下載文件中文文件名亂碼的解決方案(文件名包含很多%)
Java下載文件時(shí),文件名中文亂碼問題通常是由于編碼不正確導(dǎo)致的,使用`URLEncoder.encode(filepath, "UTF-8")`可以解決在提示下載框中正確顯示漢字文件名的問題,但在選擇直接打開時(shí),文件名會(huì)變成亂碼,解決這個(gè)問題的方法2025-02-02Java基于Google zxing生成帶logo的二維碼圖片
zxing是一個(gè)開放源碼的,用java實(shí)現(xiàn)的多種格式的1D/2D條碼圖像處理庫,本文主要介紹了Java基于Google zxing生成帶logo的二維碼圖片,具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10shade解決mybatis包沖突問題及項(xiàng)目引用的方法
這篇文章主要介紹了shade解決mybatis包沖突問題及項(xiàng)目引用的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08Java基礎(chǔ)之FileInputStream和FileOutputStream流詳解
這篇文章主要介紹了Java基礎(chǔ)之FileInputStream和FileOutputStream流詳解,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04