1. 什么是異常?
異常,就是不正常的意思。而在生活中:醫(yī)生說,你的身體某個(gè)部位有異常,該部位和正常相比有點(diǎn)不同,該部位的功能將受影響。在程序中的意思就是:
- 異常 :指的是程序在執(zhí)行過程中,出現(xiàn)的非正常的情況,最終會(huì)導(dǎo)致JVM的非正常停止。
- 異常指的并不是語法錯(cuò)誤,語法錯(cuò)了,編譯不通過,不會(huì)產(chǎn)生字節(jié)碼文件,根本不能運(yùn)行。
- 在Java等面向?qū)ο蟮木幊陶Z言中,異常本身是一個(gè)類,產(chǎn)生異常就是創(chuàng)建異常對(duì)象并拋出了一個(gè)異常對(duì)象。
- Java為異常設(shè)計(jì)了一套異常處理機(jī)制,當(dāng)程序運(yùn)行過程中發(fā)生一些異常情況時(shí),程序不會(huì)返回任何值,而是拋出封裝了錯(cuò)誤信息的異常對(duì)象。這樣保證程序代碼更加優(yōu)雅,并提高程序健壯性。為什么要設(shè)計(jì)異常呢?首先,引入異常之后,我們就可以把錯(cuò)誤代碼從正常代碼中分離出來進(jìn)行單獨(dú)處理,這樣使代碼變得更加整潔;其次,當(dāng)出現(xiàn)一些特殊情況時(shí),我們還可以拋出一個(gè)檢查異常,告知調(diào)用者讓其處理。
2. 異常的體系
- 我們先來看下異常體系結(jié)構(gòu):
- 可以看出異常類的整體繼承關(guān)系,當(dāng)然上圖不是所有的異常,也有很多子類沒有列出,這里先列出了比較常用的異常類。當(dāng)然,用戶自己也可以自定義異常實(shí)現(xiàn)。
2.1 Throwable
所有的異常都是從Throwable繼承而來的,是所有所有錯(cuò)誤與異常的超類。Throwable包含了其線程創(chuàng)建時(shí)線程執(zhí)行堆棧的快照,它提供了 printStackTrace()等接口用于獲取堆棧跟蹤數(shù)據(jù)等信息。
- 而Throwable體系下包含有兩個(gè)子類,Error(錯(cuò)誤)和Exception(異常),它們通常用于指示發(fā)生了異常情況。二者都是 Java 異常處理的重要子類,各自都包含大量子類。
2.2 Error(錯(cuò)誤)
- 定義:Error類及其子類。程序中無法處理的錯(cuò)誤,表示運(yùn)行應(yīng)用程序中出現(xiàn)了嚴(yán)重的錯(cuò)誤。大多數(shù)錯(cuò)誤與代碼編寫者執(zhí)行的操作無關(guān),而表示代碼運(yùn)行時(shí) JVM出現(xiàn)的問題。
- 特點(diǎn):對(duì)于所有的編譯時(shí)期的錯(cuò)誤以及系統(tǒng)錯(cuò)誤都是通過Error拋出的。這些錯(cuò)誤表示故障發(fā)生于虛擬機(jī)自身、或者發(fā)生在虛擬機(jī)試圖執(zhí)行應(yīng)用時(shí)。通常有如Virtual MachineError (虛擬機(jī)運(yùn)行錯(cuò)誤)等。當(dāng) JVM不再有繼續(xù)執(zhí)行操作所需的內(nèi)存資源時(shí),將出現(xiàn) OutOfMemoryError(內(nèi)存不出錯(cuò)誤),還有StackOverflowError(棧溢出錯(cuò)誤)等。這些異常發(fā)生時(shí),JVM一般會(huì)選擇線程終止。
- 注意:這些錯(cuò)誤是不受檢異常,非代碼性錯(cuò)誤,不可查的。因?yàn)樗鼈冊(cè)趹?yīng)用程序的控制和處理能力之 外,而且絕大多數(shù)是程序運(yùn)行時(shí)不允許出現(xiàn)的狀況。對(duì)于設(shè)計(jì)合理的應(yīng)用程序來說,即使確實(shí)發(fā)生了錯(cuò)誤,本質(zhì)上也不應(yīng)該試圖去處理它所引起的異常狀況。因此,當(dāng)此類錯(cuò)誤發(fā)生時(shí),應(yīng)用程序不應(yīng)該去處理此類錯(cuò)誤。
2.3 Exception(異常)
Exception 是另外一個(gè)非常重要的異常子類。程序本身可以捕獲并且可以處理的異常。這類異常一旦出現(xiàn),我們就要對(duì)代碼進(jìn)行更正,修復(fù)程序。Exception這種異常又分為兩類:運(yùn)行時(shí)異常和編譯時(shí)異常。
2.3.1 運(yùn)行時(shí)異常
- 定義:RuntimeException 類及其子類異常,如NullPointerException (空指針異常)、IndexOutOfBoundsException (下標(biāo)越界異常)等,表示 JVM在運(yùn)行期間可能出現(xiàn)的異常。
- 特點(diǎn):此類異常,Java 編譯器不會(huì)檢查它,屬于不受檢異常。一般是由程序邏輯錯(cuò)誤引起的,此類程序應(yīng)該從邏輯角度盡可能避免這類異常的發(fā)生。而當(dāng)程序中可能出現(xiàn)這類異常,即使沒有用try-catch 語句捕獲它,也沒有用throws 子句聲明拋出它,也會(huì)編譯通過。在程序中可以選擇捕獲處理,也可以不處理。如果產(chǎn)生運(yùn)行時(shí)異常,則需要通過修改代碼來進(jìn)行避免。例如,若會(huì)發(fā)生除數(shù)為零的情況,則需要通過代碼避免該情況的發(fā)生!
- 注意:RuntimeException 異常會(huì)由JVM自動(dòng)拋出并自動(dòng)捕獲(就算我們沒寫異常捕獲語句運(yùn)行時(shí)也會(huì)拋出錯(cuò)誤!!),此類異常的出現(xiàn)絕大數(shù)情況是代碼本身有問題,應(yīng)該從邏輯上去解決并改進(jìn)代碼。這里我們來看下運(yùn)行時(shí)異常是怎樣的,這里我想說下,出現(xiàn)異常,不要緊張,把異常的簡(jiǎn)單類名,拷貝到API中去查。然后看是什么異常。可以看出,我們的程序邏輯出現(xiàn)錯(cuò)誤,所以出現(xiàn)了算術(shù)異常。我們只要修改int b = 10就行了,或者b不等于0都可以。所以遇到異常,我們不用擔(dān)心。可以先從查看異常類名開始,看是什么異常,看是什么原因,找到我們程序出錯(cuò)的地方并進(jìn)行修改就可以正常運(yùn)行了。
- 那我們什么都沒有處理,那出現(xiàn)異常時(shí),是誰處理了這個(gè)異常呢?是JVM的默認(rèn)處理:把異常的名稱,原因,位置等信息輸出在控制臺(tái),但是呢程序就不能繼續(xù)執(zhí)行了。
2.3.2 非運(yùn)行時(shí)異常(編譯時(shí)異常)
- 定義:Exception中除 RuntimeException 及其子類之外的異常。
- 特點(diǎn):此類異常, Java 編譯器會(huì)檢查它。如果程序中出現(xiàn)此類異常,從程序語法角度講是必須進(jìn)行處理的異常。例如:ClassNotFoundException(沒有找到指定的類異常),IOException(IO流異常),要么通過throws 進(jìn)行聲明拋出,要么通過try-catch進(jìn)行捕獲處理,否則不能通過編譯。
- 注意:在程序中,通常我們不會(huì)自定義該類異常,而是直接使用系統(tǒng)提供的異常類。該異常我們必須手動(dòng)在代碼里添加捕獲語句來處理該異常。通過注釋可以看到,createNewFile() 方法是處理了IOException異常的,而IOException異常又繼承來自Exception,是非運(yùn)行時(shí)異常,所以必須處理異常。所以我們?nèi)绻蔷幾g時(shí)異常,在編譯時(shí)期就報(bào)錯(cuò)了,必須處理這個(gè)異常,不然程序不能編譯通過。
2.4 受檢異常與非受檢異常
通常,Java的異常(Throwable)分為受檢異常(checked exceptions)和非受檢異常(unchecked exceptions)。
2.4.1 受檢異常
編譯器要求必須處理的異常。
- 正確的程序在運(yùn)行過程中,經(jīng)常容易出現(xiàn)的、符合預(yù)期的異常情況。一旦發(fā)生此類異常,就必須采用某種方式進(jìn)行處理。除了Exception中的 RuntimeException 及其子類以外,其他的 Exception類及其子類異常就是非運(yùn)行時(shí)期異常都屬于受檢異常。
- 這種異常編譯器會(huì)檢查它,也就是說當(dāng)編譯器檢查到應(yīng)用中的某處可能會(huì)此類異常時(shí),將會(huì)提示你處理本異常——要么使用try-catch捕獲,要么使用方法簽名中用 throws 關(guān)鍵字拋出,否則編譯不通過。
2.4.2 非受檢異常
編譯器不會(huì)進(jìn)行檢查并且不要求必須處理的異常。
- 此類異常,就是當(dāng)程序中出現(xiàn)此類異常時(shí),即使我們沒有try-catch捕獲它,也沒有使用throws 拋出該異常,編譯也會(huì)正常通過。該類異常包括運(yùn)行時(shí)異常(RuntimeException 極其子類)和錯(cuò)誤( Error)。RuntimeException 發(fā)生的時(shí)候,表示程序中出現(xiàn)了編程錯(cuò)誤,所以應(yīng)該找出錯(cuò)誤修改程序,而不是去捕獲RuntimeException 。
3. 異常的處理機(jī)制(重點(diǎn))
在 Java 應(yīng)用程序中,異常處理機(jī)制為:拋出異常,捕捉異常。
3.1 Java異常處理

- 在Java中,一旦方法拋出異常,系統(tǒng)自動(dòng)根據(jù)該異常對(duì)象尋找合適異常處理器(Exception Handler)來處理該異常,把各種不同的異常進(jìn)行分類,并提供了良好的接口。
- 在 Java 中,每個(gè)異常都是一個(gè)對(duì)象,它是 Throwable類或其子類的實(shí)例。當(dāng)一個(gè)方法出現(xiàn)異常后便拋出一個(gè)異常對(duì)象,該對(duì)象中包含有異常信息,調(diào)用這個(gè)對(duì)象的方法可以捕獲到這個(gè)異常并可以對(duì)其進(jìn)行處理。
- Java 的異常處理涉及了 5 個(gè)關(guān)鍵詞:try、catch、 finally、throw 和throws。
- 在Java應(yīng)用中,異常的處理機(jī)制分為聲明異常throws,拋出異常throw 和捕獲異常try、catch、 finally。接下來讓我為大家詳細(xì)講述吧。
3.2 異常處理的關(guān)鍵詞
- throw : 用于拋出異常。
- try : 用于監(jiān)聽。將要被監(jiān)聽的代碼(可能拋出異常的代碼)放在try語句塊之內(nèi),當(dāng)try語句塊內(nèi)發(fā)生異常時(shí),異常就被拋出。
- catch :用于捕獲異常。catch用來捕獲try語句塊中發(fā)生的異常。
- finally : finally語句塊總是會(huì)被執(zhí)行。它主要用于回收在try塊里打開的資源(如數(shù)據(jù)庫連接、網(wǎng)絡(luò)連接和磁盤文件)。注意:只有finally塊,執(zhí)行完成之后,才會(huì)回來執(zhí)行try或者catch塊中的return或者throw 語句,如果finally中使用了return或者throw等終止方法的語句,則就不會(huì)跳回執(zhí)行,直接停止。
- throws: 用在方法簽名中,用于聲明該方法可能拋出的異常。
這里先了解下關(guān)鍵詞,具體定義格式和使用方法在下面介紹:
3.3 拋出異常throw
- 那什么時(shí)候使用呢?作為一個(gè)合格的程序員(這不就是我嗎),在編寫程序時(shí),我們必須要考慮程序出現(xiàn)問題的情況。比如,在定義方法時(shí),方法需要接受參數(shù)。那么,當(dāng)調(diào)用方法使用接受到的參數(shù)時(shí),首先需要先對(duì)參數(shù)數(shù)據(jù)進(jìn)行合法的判斷,數(shù)據(jù)若不合法,就應(yīng)該告訴調(diào)用者,傳遞合法的數(shù)據(jù)進(jìn)來。這時(shí)需要使用拋出異常的方式來告訴調(diào)用者。或者當(dāng)你覺得解決不了某些異常問題,且不需要調(diào)用者處理,那么你也可以拋出異常。
- throw的作用:在方法內(nèi)部拋出一個(gè)Throwable 類型的異常。任何Java代碼都可以通過throw語句拋出異常。
- 具體如何拋出一個(gè)異常呢?創(chuàng)建一個(gè)異常對(duì)象。封裝一些提示信息(信息可以自己編寫)。需要將這個(gè)異常對(duì)象告知給調(diào)用者。怎么告知呢?怎么將這個(gè)異常對(duì)象傳遞到調(diào)用者處呢?通過關(guān)鍵字throw就可以完成。throw異常對(duì)象。throw用在方法內(nèi),用來拋出一個(gè)異常對(duì)象,將這個(gè)異常對(duì)象傳遞到調(diào)用者處,并結(jié)束當(dāng)前方法的執(zhí)行。
- 定義格式:throw new 異常類名(參數(shù));
- 例子:throw new NullPointerException(“要訪問的arr數(shù)組不存在”??;
throw new ArrayIndexOutOfBoundsException(“該索引在數(shù)組中不存在,已超出范圍”??;
接下來用程序具體演示下吧:
public class ThrowDemo {
public static void main(String[] args) {
//創(chuàng)建一個(gè)數(shù)組
int[] arr = {2,4,52,2};
//根據(jù)索引找對(duì)應(yīng)的元素
int index = 4;
int element = getElement(arr, index);
System.out.println(element);
System.out.println("over");
}
/*
* 根據(jù) 索引找到數(shù)組中對(duì)應(yīng)的元素
*/
public static int getElement(int[] arr,int index){
//判斷索引是否越界
if(index<0 || index>arr.length-1){
/*
判斷條件如果滿足,當(dāng)執(zhí)行完throw拋出異常對(duì)象后,方法已經(jīng)無法繼續(xù)運(yùn)算。
這時(shí)就會(huì)結(jié)束當(dāng)前方法的執(zhí)行,并將異常告知給調(diào)用者。這時(shí)就需要通過異常來解決。
*/
throw new ArrayIndexOutOfBoundsException("老弟,你的索引越界了,別這么干");
}
int element = arr[index];
return element;
}
}
運(yùn)行后輸出結(jié)果為:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 老弟,你的索引越界了,別這么干
at com.it.test2.ThrowDemo.getElement(ThrowDemo.java:25)
at com.it.test2.ThrowDemo.main(ThrowDemo.java:10)
可以看到我定義了索引為4,但是數(shù)組的長(zhǎng)度只有4。所以會(huì)報(bào)錯(cuò)。
注意:所以如果產(chǎn)生了問題,我們就會(huì)throw將問題描述類即異常進(jìn)行拋出,也就是將問題返回給該方法的調(diào)用者。結(jié)果是
ArrayIndexOutOfBoundsException 的數(shù)組索引越界的問題。
那么對(duì)于調(diào)用者來說,該怎么處理呢?一種是進(jìn)行捕獲處理,另一種就是繼續(xù)講問題聲明出去,使用throws聲明處理。
3.4 聲明異常throws
如果一個(gè)方法可能會(huì)出現(xiàn)異常,但沒有能力處理這種異常,可以在方法聲明處用throws子句來聲明拋出異常。例如汽車在運(yùn)行時(shí)它可能會(huì)出現(xiàn)故障,汽車本身沒辦法處理這個(gè)故障,那就讓開車的人來處理。
- 聲明異常:將問題標(biāo)識(shí)出來,報(bào)告給調(diào)用者。如果方法內(nèi)通過throw拋出了編譯時(shí)異常,而沒有捕獲處理(稍后講解該方式),那么必須通過throws進(jìn)行聲明,讓調(diào)用者去處理。關(guān)鍵字throws運(yùn)用于方法聲明之上,用于表示當(dāng)前方法不處理異常,而是提醒該方法的調(diào)用者來處理異常(拋出異常).
- 定義格式: throws語句用在方法定義時(shí)聲明該方法要拋出的異常類型,如果拋出的是Exception異常類型,則該方法被聲明為拋出所有的異常。多個(gè)異常可使用逗號(hào)分割。修飾符 返回值類型 方法名(參數(shù)) throws 異常類名1,異常類名2…{ } 注意:當(dāng)方法拋出異常列表的異常時(shí),方法將不對(duì)這些類型及其子類類型的異常作處理,而拋向調(diào)用該方法的方法,由他去處理。使用throws關(guān)鍵字將異常拋給調(diào)用者后,如果調(diào)用者不想處理該異常,可以繼續(xù)向上拋出,但最終要有能夠處理該異常的調(diào)用者。比如汽車壞了,開車的人也不會(huì)修理,只能叫修車公司來修理了。
- 演示一下:一般來說,throws和 throw通常是成對(duì)出現(xiàn)的,例如:public class ThrowsDemo { public static void main(String[] args) throws FileNotFoundException { readFile(“a.txt”??; }public class ThrowsDemo {
public static void main(String[] args) throws FileNotFoundException { readFile(“a.txt”??; } // 如果定義功能時(shí)有問題發(fā)生需要報(bào)告給調(diào)用者。可以通過在方法上使用throws關(guān)鍵字進(jìn)行聲明 public static void readFile(String path) throws FileNotFoundException { if(!path.equals(“a.txt”??) {//如果不是 a.txt這個(gè)文件 // 我假設(shè) 如果不是 a.txt 認(rèn)為 該文件不存在 是一個(gè)錯(cuò)誤 也就是異常 throw throw new FileNotFoundException(“文件不存在”??; } }}
而throws用于進(jìn)行異常類的聲明,若該方法可能有多種異常情況產(chǎn)生,那么在throws后面可以寫多個(gè)異常類,用逗號(hào)隔開。
public class ThrowsDemo2 {
public static void main(String[] args) throws IOException {
readFile("a.txt");
}
//若該方法可能有多種異常情況產(chǎn)生,那么在throws后面可以寫多個(gè)異常類,用逗號(hào)隔開
//若有異常a是異常b的子類,也可以直接省略,寫b異常
private static void readFile(String path) throws FileNotFoundException, IOException {
if (!path.equals("a.txt")) {//如果不是 a.txt這個(gè)文件
// 我假設(shè) 如果不是 a.txt 認(rèn)為 該文件不存在 是一個(gè)錯(cuò)誤 也就是異常 throw
throw new FileNotFoundException("文件不存在");
}
if (!path.equals("b.txt")) {
throw new IOException();
}
}
}
throws拋出異常的規(guī)則:
- 如果是非受檢異常(unchecked exception),即Error、RuntimeException或它們的子類,那么可以不使用throws關(guān)鍵字來聲明要拋出的異常,編譯仍能順利通過,但在運(yùn)行時(shí)會(huì)被系統(tǒng)拋出。
- 如果一個(gè)方法可能出現(xiàn)受檢異常(checked exception),要么用try-catch語句捕獲,要么用throws子句聲明將它拋出,否則會(huì)導(dǎo)致編譯錯(cuò)誤。
- 只有當(dāng)拋出了異常時(shí),該方法的調(diào)用者才必須處理或者重新拋出該異常。若當(dāng)方法的調(diào)用者無力處理該異常的時(shí)候,應(yīng)該繼續(xù)拋出。
- 調(diào)用方法必須遵循任何可查異常的處理和聲明規(guī)則。若覆蓋一個(gè)方法,則不聲明與覆蓋方法不同的異常。聲明的任何異常必須是被覆蓋方法所聲明異常的同類或子類。
3.5 捕獲異常try 、finally 、catch
這三個(gè)關(guān)鍵字主要有下面幾種組合方式try-catch 、try-finally、try-catch-finally。
注意:catch語句可以有一個(gè)或者多個(gè)或者沒有,finally至多有一個(gè),try必要有。
那這里你會(huì)問有沒有單獨(dú)try模塊出現(xiàn),那我想問下你,try是用來監(jiān)聽是否有異常,那如果發(fā)生了異常,誰來捕獲呢?所以沒有try單獨(dú)出現(xiàn)。而且編譯不能通過。
所以跟try模塊一樣,例如catch,finally也不能單獨(dú)使用出現(xiàn)

- 程序通常在運(yùn)行之前不報(bào)錯(cuò),但是運(yùn)行后可能會(huì)出現(xiàn)某些未知的錯(cuò)誤,不想異常出現(xiàn)導(dǎo)致程序終止,或者不想直接拋出到上一級(jí),那么就需要通過try-catch等形式進(jìn)行異常捕獲,之后根據(jù)不同的異常情況來進(jìn)行相應(yīng)的處理。
- 捕獲異常:Java中對(duì)異常有針對(duì)性的語句進(jìn)行捕獲,可以對(duì)出現(xiàn)的異常進(jìn)行指定方式的處理。
捕獲異常語法如下:
3.5.1 try-catch 形式:
try{
編寫可能會(huì)出現(xiàn)異常的代碼
}catch(異常類型 e){
處理異常的代碼
//記錄日志/打印異常信息/繼續(xù)拋出異常
}
例如:
public class TryCatchDemo {
public static void main(String[] args) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
//當(dāng)產(chǎn)生異常時(shí),必須有處理方式。要么捕獲,要么聲明。
Date date = simpleDateFormat.parse("2020-10-06");
} catch (ParseException e) {// 括號(hào)中需要定義什么呢?
//try中拋出的是什么異常,在括號(hào)中就定義什么異常類型
e.printStackTrace();//記錄日志/打印異常信息/繼續(xù)拋出異常
}
/*
public Date parse(String source) throws ParseException{}
//parse拋出了ParseException異常
public class ParseException extends Exception {}
*/
}
}
如何獲取異常信息:
Throwable類中定義了一些查看方法:
- public String getMessage():獲取異常的描述信息,原因(提示給用戶的時(shí)候,就提示錯(cuò)誤原因。
- public String toString():獲取異常的類型和異常描述信息。
- public void printStackTrace():打印異常的跟蹤棧信息并輸出到控制臺(tái)。
- 具體我們可以來看下:
public class TryCathDemo2 {
public static void main(String[] args) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
//當(dāng)產(chǎn)生異常時(shí),必須有處理方式。要么捕獲,要么聲明。
//演示下獲取異常信息,修改了格式。
Date date = simpleDateFormat.parse("2020年10月06");
} catch (ParseException e) {
//public String getMessage():獲取異常的描述信息,原因(提示給用戶的時(shí)候,就提示錯(cuò)誤原因
System.out.println(e.getMessage());//Unparseable date: "2020年10月06"
System.out.println(e.toString());//java.text.ParseException: Unparseable date: "2020年10月06"
e.printStackTrace();//輸出信息而且飄紅!!!
/*
java.text.ParseException: Unparseable date: "2020年10月06"
at java.text.DateFormat.parse(DateFormat.java:366)
at com.it.test3.TryCathDemo2.main(TryCathDemo2.java:13)
*/
}
}
}
而如果有多個(gè)異常使用捕獲我們又該如何處理呢?
- 多個(gè)異常分別處理。
- 多個(gè)異常一次捕獲,多次處理。
一般我們是使用一次捕獲多次處理方式,格式如下:
try{
編寫可能會(huì)出現(xiàn)異常的代碼
}catch(異常類型A e){ 當(dāng)try中出現(xiàn)A類型異常,就用該catch來捕獲.
處理異常的代碼
//記錄日志/打印異常信息/繼續(xù)拋出異常
}catch(異常類型B e){ 當(dāng)try中出現(xiàn)B類型異常,就用該catch來捕獲.
處理異常的代碼
//記錄日志/打印異常信息/繼續(xù)拋出異常
}
例如:
public class TryCatchDemo3 {
public static void main(String[] args) {
//test();
test2();
}
//多個(gè)異常一次捕獲,多次處理。
public static void test2(){
int[] arr = {11, 22, 66, 0};
try {
//System.out.println(arr[5]);//一旦這個(gè)報(bào)錯(cuò),下面的代碼就不會(huì)執(zhí)行
System.out.println(arr[2]);
System.out.println(arr[0] / arr[arr.length-1]);
} catch (ArithmeticException e) {
System.out.println("除數(shù)不為0");
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("數(shù)組下標(biāo)越界");
}catch (Exception e) {
e.printStackTrace();
}
}
//分別處理的方式
public static void test() {
int a = 10;
int b = 0;
try {
System.out.println(a / b);
} catch (ArithmeticException e) {
System.out.println("除數(shù)不為0");//除數(shù)不為0
}
int[] arr = {1, 2, 3};
try {
System.out.println(arr[4]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("數(shù)組下標(biāo)越界");//數(shù)組下標(biāo)越界
}
}
}
注意:一次捕獲,多次處理的異常處理方式,要求多個(gè)catch中的異常不能相同,并且若catch中的多個(gè)異常之間有子父類異常的關(guān)系,那么子類異常要求在上面的catch處理,父類異常在下面的catch處理。
例如:

3.5.2 try-finally 形式:
try{
//(嘗試運(yùn)行的)程序代碼
}finally{
//異常發(fā)生,總是要執(zhí)行的代碼
}
try-finally表示對(duì)一段代碼不管執(zhí)行情況如何,都會(huì)走 finally 中的代碼,
例如:
public class TryFinallyDemo {
public static void main(String[] args) {
int a = 10;
int b = 0;
try{
System.out.println(a / b);
System.out.println("會(huì)走try嗎");
}finally{
System.out.println("會(huì)finally嗎");//會(huì)finally嗎
}
System.out.println("會(huì)走外面嗎");
/*
沒有捕獲的話,他只會(huì)走finally語句然后報(bào)出異常。
會(huì)finally嗎
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.it.test3.TryFinallyDemo.main(TryFinallyDemo.java:8)
*/
}
}
可以看到程序異常了,還是會(huì)去走finally語句塊的代碼。
3.5.3 try-catch-finally 形式:
try {
// 可能會(huì)發(fā)生異常的程序代碼
} catch (異常類型A e){
// 捕獲并處置try拋出的異常類型A
} finally {
// 無論是否發(fā)生異常,都將執(zhí)行的語句塊
}
跟try-finally一樣表示對(duì)一段代碼不管執(zhí)行情況如何,都會(huì)走 finally 中的代碼。
當(dāng)方法中發(fā)生異常,異常處之后的代碼不會(huì)再執(zhí)行,如果之前獲取了一些本地資源需要釋放,則需要在方法正常結(jié)束時(shí)和 catch 語句中都調(diào)用釋放本地資源的代碼,顯得代碼比較繁瑣,finally 語句可以解決這個(gè)問題。
例如:
public class TryCatchFinallyDemo {
public static void main(String[] args) {
test();
}
public static void test() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
//date = simpleDateFormat.parse("2020-10-06");//第一次運(yùn)行成功
date = simpleDateFormat.parse("2020年10月06日");
} catch (ParseException e) {
e.printStackTrace();
}finally{
System.out.println("finally這里一定會(huì)執(zhí)行");
}
System.out.println("會(huì)走外面這里嗎" + date);
}
}
運(yùn)行成功的代碼后結(jié)果:
finally這里一定會(huì)執(zhí)行
會(huì)走外面這里嗎Tue Oct 06 00:00:00 CST 2020
運(yùn)行失敗的代碼后結(jié)果:
java.text.ParseException: Unparseable date: "2020/10/06"
at java.text.DateFormat.parse(DateFormat.java:366)
at com.it.test3.TryCatchFinallyDemo.test(TryCatchFinallyDemo.java:19)
at com.it.test3.TryCatchFinallyDemo.main(TryCatchFinallyDemo.java:12)
finally這里一定會(huì)執(zhí)行
會(huì)走外面這里嗎null
可以看到,無論失敗,都會(huì)執(zhí)行finally語句塊的代碼。
- 注意:try-catch-finally中,如果catch中 return了,finally還會(huì)執(zhí)行嗎?public class TryCatchFinallyDemo2 {
public static void main(String[] args) { test(); } public static void test() { int a = 10; try{ System.out.println(a / 0); }catch(ArithmeticException e) { e.printStackTrace(); return ; }finally { System.out.println(“finally”??; } }}
運(yùn)行結(jié)果:
java.lang.ArithmeticException: / by zero
at com.it.test3.TryCatchFinallyDemo2.test(TryCatchFinallyDemo2.java:11)
at com.it.test3.TryCatchFinallyDemo2.main(TryCatchFinallyDemo2.java:5)
finally
可以看到,就算catch中 return了,finally也會(huì)執(zhí)行。
那finally是在return前呢,還是return后呢?
讓我們看下面的代碼?
public class TryCatchFinallyDemo2 {
public static void main(String[] args) {
// test();
System.out.println(test2()); // 我有執(zhí)行到嗎 try
System.out.println(test3()); // 我有執(zhí)行到嗎 catch
}
public static String test3() {
String str = "";
try {
str = "try";
System.out.println(10 / 0);
return str;
}catch(Exception e) {
str = "catch";
return str;
}finally {
str = "finally";
System.out.println("我有執(zhí)行到嗎");
}
}
public static String test2() {
String str = "";
try {
str = "try";
return str;
}catch(Exception e) {
str = "catch";
return str;
}finally {
str = "finally";
System.out.println("我有執(zhí)行到嗎");
}
}
}
運(yùn)行結(jié)果:
我有執(zhí)行到嗎
try
我有執(zhí)行到嗎
catch
看到這里發(fā)現(xiàn)無論是否異常,finally都會(huì)執(zhí)行,但是都在在return之前就執(zhí)行了代碼。可是可是,為什么返回出來的字符串不是finally呢?讓我們一起來思考思考:
- 我們看test2()方法,程序執(zhí)行try語句塊,把變量str賦值為”try”,由于沒有發(fā)現(xiàn)異常,接下來執(zhí)行finally語句塊,把變量str賦值為”finally”,然后return str,則t的值是finally,最后str的值就是”finally”,程序結(jié)果應(yīng)該顯示finally,但是實(shí)際結(jié)果為“try”。
- 實(shí)際上,在try語句的return塊中,當(dāng)我們執(zhí)行到return str這一步的時(shí)候呢,這里不是return str 而是return “try”了,這個(gè)放回路徑就已經(jīng)形成了。相當(dāng)于return返回的引用變量(str是引用類型)并不是try語句外定義的引用變量str,而是系統(tǒng)重新定義了一個(gè)局部引用str2 ,返回指向了引用str2 對(duì)應(yīng)的值,也就是”try”字符串。但是到這里呢,它發(fā)現(xiàn)后面還有finally,所以繼續(xù)執(zhí)行finally的內(nèi)容,str = “finally”; System.out.println(“我有執(zhí)行到嗎”??;, 再次回到以前的路徑,繼續(xù)走return “try”,形成返回路徑之后,這里的return的返回引用就不是變量str 了,而是str2引用的值”try”字符串。
是不是對(duì)這個(gè)現(xiàn)象有了一定的了解。嘿嘿,這里我們?cè)俎D(zhuǎn)換下:
public class TryCatchFinallyDemo2 {
public static void main(String[] args) {
// test();
// System.out.println(test2()); // try
// System.out.println(test3()); // catch
System.out.println(test4());
}
public static String test4() {
String str = "";
try {
str = "try";
return str;
}catch(Exception e) {
str = "catch";
return str;
}finally {
str = "finally";
return str;
}
}
}
這里我們猜測(cè)下,結(jié)果是什么呢?
運(yùn)行結(jié)果:finally
- 我們發(fā)現(xiàn)try語句中的return語句給忽略。可能JVM認(rèn)為一個(gè)方法里面有兩個(gè)return語句并沒有太大的意義,所以try中的return語句給忽略了,直接起作用的是最后finally中的return語句,就又重新形成了一條返回路徑,所以這次返回的是“finally”字符串。
再看一下: 我們是不是知道finally語句是一定執(zhí)行的,但是能有辦法使他不執(zhí)行嗎?
既然我說了,那么就是一定有的啦。
public class TryCatchFinallyDemo3 {
public static void main(String[] args) {
try{
System.out.println(10 / 0);
}catch(Exception e) {
e.printStackTrace();
System.exit(0);
}finally {
System.out.println("finally我有執(zhí)行到嗎");
}
}
}
執(zhí)行結(jié)果:
java.lang.ArithmeticException: / by zero
at com.it.test3.TryCatchFinallyDemo3.main(TryCatchFinallyDemo3.java:6)
可以發(fā)現(xiàn):
當(dāng)只有在try或者catch中調(diào)用退出JVM的相關(guān)方法,此時(shí)finally才不會(huì)執(zhí)行,否則finally永遠(yuǎn)會(huì)執(zhí)行。
3.5.4 小結(jié)
- try塊:用于捕獲異常。其后可接零個(gè)或多個(gè)catch塊,如果沒有catch塊,則必須跟一個(gè)finally塊。
- catch塊:用于處理try捕獲到的異常。
- finally塊:無論是否捕獲或處理異常,finally塊里的語句都會(huì)被執(zhí)行。當(dāng)在try塊或catch塊中遇到return語句時(shí),finally語句塊將在方法返回之前被執(zhí)行。在以下4種特殊情況下,finally塊不會(huì)被執(zhí)行:在finally語句塊中發(fā)生了異常。在前面的代碼中用了System.exit()退出程序。程序所在的線程死亡。關(guān)閉CPU。
3.6 如何選擇異常類型
可以根據(jù)下圖來選擇是捕獲異常,聲明異常還是拋出異常

我們?cè)谌粘L幚懋惓5拇a中,應(yīng)該遵循的原則:
- 不要捕獲類似Exception 之類的異常,而應(yīng)該捕獲類似特定的異常,方便排查問題,而且也能夠讓其他人接手你的代碼時(shí),會(huì)減少罵你的次數(shù)。
- 不要生吞異常。這是異常處理中要特別注重的事情。如果我們不把異常拋出來,或者也沒有輸出到日志中,程序可能會(huì)在后面以不可控的方式結(jié)束。有時(shí)候需要線上調(diào)試代碼。
4 JDK1.7有關(guān)異常新特性
4.1 try-with-resources
Java 類庫中有許多資源需要通過 close 方法進(jìn)行關(guān)閉。比如 InputStream、OutputStream等。作為開發(fā)人員經(jīng)常會(huì)忽略掉資源的關(guān)閉方法,導(dǎo)致內(nèi)存泄漏。當(dāng)然不是我啦!
在JDK1.7之前呢,try-catch-finally語句是確保資源會(huì)被關(guān)閉的最佳方法,就算異常或者返回也一樣可以關(guān)閉資源。
- 讓我們先看看之前我們?nèi)绾侮P(guān)閉資源吧:public static String readFile(String path) {
BufferedReader br = null; try { br = new BufferedReader(new FileReader(path)); return br.readLine(); } catch (IOException e) { e.printStackTrace(); } finally {//必須在這里關(guān)閉資源 if (br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } return null; }}
是不是我們必須finally語句塊中手動(dòng)關(guān)閉資源,否則會(huì)導(dǎo)致資源的泄露
在JDK1.7及以后的版本:
JDK1.7 中引入了try-with-resources 語句。
- try-with-resources 語句是一個(gè)聲明一個(gè)或多個(gè)資源的try語句。try-with-resources 語句確保在語句的最后每個(gè)資源都被關(guān)閉,只要是實(shí)現(xiàn)了AutoCloseable接口或者是Closeable接口的對(duì)象都可以使用try-with-resources 來實(shí)現(xiàn)異常處理和關(guān)閉資源。
- 實(shí)際上,在編譯時(shí)也會(huì)進(jìn)行轉(zhuǎn)化為try-catch-finally語句。
那我們來看看怎么使用吧:
格式:
try (創(chuàng)建流對(duì)象語句,如果多個(gè),使用';'隔開) {
// 讀寫數(shù)據(jù)
} catch (IOException e) {
e.printStackTrace();
}
演示下:
/**
* JDK1.7之后就可以使用try-with-resources,不需要
* 我們?cè)趂inally塊中手動(dòng)關(guān)閉資源
*/
public class TryWithResourcesDemo {
public static String readLineFormFile(String path) {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
兩者的對(duì)比:
- 代碼精煉,在JDK1.7之前都有finally塊,如果使用一些框架可能會(huì)將finally塊交由框架處理,如Spring。JDK1.7及以后的版本只要資源類實(shí)現(xiàn)了AutoCloseable或Closeable程序在執(zhí)行完try塊后會(huì)自動(dòng)close()所使用的資源無論br.readLine()是否拋出異常。
- 代碼更完全。在出現(xiàn)資源泄漏的程序中,很多情況是開發(fā)人員沒有或者開發(fā)人員沒有正確的關(guān)閉資源所導(dǎo)致的。JDK1.7之后采用try-with-resources 的方式,則可以將資源關(guān)閉這種與業(yè)務(wù)實(shí)現(xiàn)沒有很大直接關(guān)系的工作交給JVM 完成。省去了部分開發(fā)中可能出現(xiàn)的代碼風(fēng)險(xiǎn)。
- 以readLineFormFile方法為例,如果調(diào)用 readLine()和 close()方法都拋出異常,后一個(gè)異常就會(huì)被禁止,以保留第一個(gè)異常。
4.2 catch多種異常并拋出新的異常
- 在JDK1.7之前catch 多個(gè)異常是這樣的:try{
編寫可能會(huì)出現(xiàn)異常的代碼
}catch(異常類型A e){ 當(dāng)try中出現(xiàn)A類型異常,就用該catch來捕獲.
處理異常的代碼
}catch(異常類型B e){ 當(dāng)try中出現(xiàn)B類型異常,就用該catch來捕獲.
處理異常的代碼
} - JDK1.7及以后可以這樣:try{
編寫可能會(huì)出現(xiàn)異常的代碼
}catch (異常類型A | 異常類型B e) {
處理異常的代碼
}但是呢,這個(gè)是同類型異常才可以這樣定義。
5 自定義異常
5.1 為什么需要自定義異常類:
我們說了Java中不同的異常類,分別表示著某一種具體的異常情況,那么在開發(fā)中,當(dāng)Java內(nèi)置的異常都不能明確的說明異常情況的時(shí)候,需要?jiǎng)?chuàng)建自己的異常。例如年齡負(fù)數(shù)問題,考試成績(jī)負(fù)數(shù)問題。
什么是自定義異常類呢:
在開發(fā)中根據(jù)自己業(yè)務(wù)的異常情況來自己定義異常類。例如:登錄系統(tǒng)中,年齡能為負(fù)數(shù)嗎,不能就需要自己定義一個(gè)登錄異常類。
5.2 怎樣定義自定義異常類
- 自定義一個(gè)編譯期異常: 自定義類并繼承于java.lang.Exception 。
- 自定義一個(gè)運(yùn)行時(shí)期的異常類:自定義類并繼承于java.lang.RuntimeException 。
- 一般定義一個(gè)異常類需要包含兩個(gè)構(gòu)造函數(shù):一個(gè)無參構(gòu)造函數(shù)和一個(gè)帶有詳細(xì)描述信息的構(gòu)造函數(shù)
public class MyException extends Exception {
public MyException(){ }
public MyException(String message){
super(message);
}
}
5.3 自定義異常的例子
需求:我們模擬登陸操作,如果用戶名已存在,則拋出異常并提示:親,該用戶名已經(jīng)被注冊(cè)。這個(gè)相信大家也經(jīng)常見到吧。
- 首先定義一個(gè)登陸異常類LoginException :/**
* 登陸異常類*/public class LoginException extends Exception { public LoginException() { } public LoginException(String message) { super(message); }}
模擬登陸操作,使用數(shù)組模擬數(shù)據(jù)庫中存儲(chǔ)的數(shù)據(jù),并提供當(dāng)前注冊(cè)賬號(hào)是否存在方法用于判斷。
public class LoginTest {
// 模擬數(shù)據(jù)庫中已存在賬號(hào)
private static String[] names = {"hello", "world", "fish"};
public static void main(String[] args) {
//調(diào)用方法
try{
// 可能出現(xiàn)異常的代碼
checkUsername("fish");
System.out.println("注冊(cè)成功");//如果沒有異常就是注冊(cè)成功
} catch(LoginException e) {
//處理異常
e.printStackTrace();
}
}
//判斷當(dāng)前注冊(cè)賬號(hào)是否存在
//因?yàn)槭蔷幾g期異常,又想調(diào)用者去處理 所以聲明該異常
public static boolean checkUsername(String uname) throws LoginException {
for (String name : names) {
if(name.equals(uname)){//如果名字在這里面 就拋出登陸異常
throw new LoginException("親"+name+"已經(jīng)被注冊(cè)了!");
}
}
return true;
}
}
執(zhí)行結(jié)果:注冊(cè)成功。
聲明:本文由網(wǎng)站用戶竹子發(fā)表,超夢(mèng)電商平臺(tái)僅提供信息存儲(chǔ)服務(wù),版權(quán)歸原作者所有。若發(fā)現(xiàn)本站文章存在版權(quán)問題,如發(fā)現(xiàn)文章、圖片等侵權(quán)行為,請(qǐng)聯(lián)系我們刪除。