異常分類
Java將異常分為兩種,Checked異常和Runtime異常。Java認(rèn)為Checked異常都是可以在編譯階段被處理的異常,所以它強(qiáng)制程序處理所有的Checked異常;而Runtime異常則無須處理。
try-catch塊
如果執(zhí)行try塊里的業(yè)務(wù)邏輯代碼時出現(xiàn)異常,系統(tǒng)自動生成一個異常對象,該異常對象被提交給Java運(yùn)行時環(huán)境,這個過程被稱為拋出異常,同時try塊后續(xù)代碼通常將得不到執(zhí)行。當(dāng)Java運(yùn)行時環(huán)境收到異常對象時,會尋找能處理該異常對象的catch塊,如果找到合適的catch塊,則把該異常對象交給該catch塊處理,這個過程被稱為捕獲異常:如果Java運(yùn)行時環(huán)境找不到捕獲異常的catch塊,則運(yùn)行時環(huán)境終止,Java程序也將退出。
注意不管程序代碼塊是否處于try塊中,甚至包括catch塊中的代碼,只要執(zhí)行該代碼塊時出現(xiàn)了異常,系統(tǒng)總會自動生成一個異常對象。如果程序沒有為這段代碼定義任何的catch塊,則Java運(yùn)行時環(huán)境無法找到處理該異常的catch塊,程序就在此退出。
當(dāng)Java運(yùn)行時環(huán)境接收到異常對象后,會依次判斷該異常對象是否是catch塊后異常類或其子類的實例,如果是,Java運(yùn)行時環(huán)境將調(diào)用該catch塊來處理該異常,否則再次拿該異常對象和下一個catch塊里的異常類進(jìn)行比較。try塊后可以有多個catch塊,這是為了針對不同的異常類提供不同的異常處理方式。當(dāng)系統(tǒng)發(fā)生不同的意外情況時,系統(tǒng)會生成不同的異常對象,Java運(yùn)行時就會根據(jù)該異常對象所屬的異常類來決定使用哪個catch塊來處理該異常。
注意:try塊里聲明的變量是代碼塊內(nèi)局部變量,它只在try塊內(nèi)有效,在catch塊中不能訪問該變量。
異常類的繼承體系
Java提供了豐富的異常類,這些異常類之間有嚴(yán)格的繼承關(guān)系。
Java把所有的非正常情況分成兩種:異常和錯誤,它們都繼承Throwable父類。Error錯誤一般是指與虛擬機(jī)相關(guān)的問題,如系統(tǒng)崩潰、虛擬機(jī)錯誤、動態(tài)鏈接失敗等,這種錯誤無法恢復(fù)或不可能捕獲,將導(dǎo)致應(yīng)用程序中斷。通常應(yīng)用程序無法處理這些錯誤,因此應(yīng)用程序不應(yīng)該試圖使用catch塊來捕獲Error對象。在定義該方法時,也無須在其throws子句中聲明該方法可能拋出Error異常及其任何子類。
進(jìn)行異常捕獲時不僅應(yīng)該把Exception類對應(yīng)的catch塊放在最后,而且所有父類異常的catch塊都應(yīng)該排在子類異常catch塊的后面,否則將出現(xiàn)編譯錯誤。
Java7提供的多異常捕獲
在Java7以前,每個?catch塊只能捕獲一種類型的異常,但從Java7開始,一個catch塊可以捕獲多種類型的異常。使用一個catch塊捕獲多種類型的異常時需要注意如下兩個地方:1.捕獲多種類型的異常時,多種異常類型之間用豎線|隔開。2.捕獲多種類型的異常時,異常變量有隱式的final修飾,因此程序不能對異常變量重新賦值。
使用finally回收資源
有些時候,程序在try塊里打開了一些物理資源(例如數(shù)據(jù)庫連接、網(wǎng)絡(luò)連接和磁盤文件等),這些物理資源都必須顯式回收。Java的垃圾回收機(jī)制不會回收任何物理資源,垃圾回收機(jī)制只能回收堆內(nèi)存中對象所占用的內(nèi)存。為了保證一定能回收try塊中打開的物理資源,異常處理機(jī)制提供了finally塊。不管try塊中的代碼是否出現(xiàn)異常,也不管哪一個catch塊被執(zhí)行,甚至在try塊或catch塊中執(zhí)行了return語句,finally塊總會被執(zhí)行(程序?qū)⒁\(yùn)行到return語句之前,會跳轉(zhuǎn)到finally塊中執(zhí)行,執(zhí)行完畢后會再執(zhí)行之前的return語句)。在通常情況下,一旦在方法里執(zhí)行到return語句的地方,程序?qū)⒘⒓唇Y(jié)束該方法,現(xiàn)在不會了,雖然return語句也強(qiáng)制方法結(jié)束,但一定會先執(zhí)行finally塊里的代碼,然后再執(zhí)行前面的return語句。但是如果在異常處理的catch塊或者try塊中執(zhí)行System.exit(1)語句來退出虛擬機(jī),finally塊將失去執(zhí)行的機(jī)會。
異常處理語法結(jié)構(gòu)中只有try塊是必需的,catch塊和finally塊都是可選的,但catch塊和finally塊至少出現(xiàn)其中之一。
當(dāng)Java程序執(zhí)行try塊或catch塊時遇到了return或throw語句時,這兩個語句本都會導(dǎo)致該方法立即結(jié)束,但是系統(tǒng)并不會立即執(zhí)行這兩個語句,而是去尋找該異常處理流程中是否包含finally塊,如果沒有finally塊,程序立即執(zhí)行return或throw語句,方法終止;如果有finally塊,系統(tǒng)立即開始執(zhí)行finally塊,只有當(dāng)finally塊執(zhí)行完成后,系統(tǒng)才會再次跳回來執(zhí)行try塊或catch塊里的return或throw語句;如果finally塊里也使用了return或throw等導(dǎo)致方法終止的語句,finally塊已經(jīng)終止了方法,系統(tǒng)將不會跳回去執(zhí)行try塊或catch塊里的任何代碼。
Java7的自動關(guān)閉資源的try語句
當(dāng)程序使用finally塊關(guān)閉資源時,程序顯得異常臃腫。
Java7增強(qiáng)了try語句的功能,它允許在try關(guān)鍵字后緊跟一對圓括號,圓括號中可以聲明和初始化一個或多個資源。此處的資源指的是那些必須在程序結(jié)束時顯式關(guān)閉的資源(比如數(shù)據(jù)庫連接或網(wǎng)絡(luò)連接等),try語句在自身結(jié)束時自動關(guān)閉這些資源。
需要指出的是,為了保證try語句可以正常關(guān)閉資源,這些資源實現(xiàn)類必須實現(xiàn)AutoCloseable或Closeable接口,實現(xiàn)這兩個接口就必須實現(xiàn)close()方法。Closeable是AutoCloseable的子接口,可以被自動關(guān)閉的資源類要么實現(xiàn)AutoCloseable接口,要么實現(xiàn)Closeable接口。Closeable接口里的close()方法聲明拋出了IOException,因此它的實現(xiàn)類在實現(xiàn)close()方法時只能聲明拋出IOException或其子類;AutoCloseable接口里的close()方法聲明拋出了Exception,因此它的實現(xiàn)類在實現(xiàn)close()方法時可以聲明拋出任何異常。
自動關(guān)閉資源的try語句相當(dāng)于包含了隱式的finally塊(這個finally塊用于關(guān)閉資源),因此這個try語句可以既沒有catch塊也沒有finally塊。如果程序需要,自動關(guān)閉資源的try語句后也可以帶多個catch塊和一個finally塊。
~Checked異常和Runtime異常
Java的異常被分為兩大類:Checked異常和Runtime異常。
Java認(rèn)為Checked異常都是可以被處理的異常,所以Java程序必須顯式處理Checked異常。如果程序沒有處理Checked異常,將無法通過編譯。對于Checked異常的處理方式有如下兩種:1.當(dāng)前方法明確知道如何處理該異常,程序應(yīng)該使用try...catch塊來捕獲該異常,然后在catch塊中處理該異常。2.當(dāng)前方法不知道如何處理這種異常,應(yīng)該在定義該方法時聲明拋出異常。即對于Checked異常,要么顯式聲明拋出,要么顯式捕獲并處理它。
Runtime異常則更加靈活,Runtime異常無須顯式聲明拋出,如果程序需要捕獲Runtime異常,也可以使用try...catch塊來實現(xiàn)。
使用throws聲明拋出異常
使用throws聲明拋出異常的思路是,當(dāng)前方法不知道如何處理這種類型的異常,該異常應(yīng)該由上級調(diào)用者處理;如果main方法也不知道如何處理這種類型的異常,也可以使用throws聲明拋出異常,該異常將交給JVM處理。JVM對異常的處理方法是,打印異常的跟蹤棧信息,并中止程序運(yùn)行,這就是前面程序在遇到異常后自動結(jié)束的原因。
throws聲明拋出的語法格式如下:throws ExceptionClass1,ExceptionClass2...
一旦使用throws語句聲明拋出該異常,程序就無須使用try...catch塊來捕獲該異常了。如果某段代碼中調(diào)用了一個帶throws聲明的方法,該方法聲明拋出了Checked異常,則表明該方法希望它的調(diào)用者來處理該異常。也就是說調(diào)用該方法時要么放在try塊中顯式捕獲該異常,要么放在另一個帶throws聲明拋出的方法中。
使用throws聲明拋出異常時有一個限制,就是方法重寫時的一條規(guī)則:子類方法(重寫了父類的方法)聲明拋出的異常類型應(yīng)該是父類方法聲明拋出的異常類型的子類或相同,子類方法(重寫了父類的方法)聲明拋出的異常不允許比父類方法聲明拋出的異常多。
使用Checked異常至少存在如下兩大不便之處:1.對于程序中的Checked異常,Java要求必須顯式捕獲并處理該異常或者顯式聲明拋出該異常,這樣就增加了編程復(fù)雜度。2.如果在方法中顯式聲明拋出Checked異常,將會導(dǎo)致方法簽名與異常耦合,如果該方法是重寫父類的方法,則該方法拋出的異常還會受到被重寫方法所拋出異常的限制。
在大部分時候推薦使用Runtime異常,而不使用Checked異常。尤其當(dāng)程序需要自行拋出異常時,使用Runtime異常將更加簡潔。當(dāng)使用Runtime異常時,程序無須在方法中聲明拋出Checked異常,一旦發(fā)生了自定義錯誤,程序只管拋出Runtime異常即可。如果程序需要在合適的地方捕獲異常并對異常進(jìn)行處理,則一樣可以使用try…catch塊來捕獲Runtime異常。
使用throw拋出異常
當(dāng)程序出現(xiàn)錯誤時,系統(tǒng)會自動拋出異常;除此之外,Java也允許程序自行拋出異常,自行拋出異常使用throw語句來完成。 throw語句拋出的是一個異常實例,而且每次只能拋出一個異常實例。當(dāng)Java運(yùn)行時接收到開發(fā)者自行拋出的異常時,同樣會中止當(dāng)前的執(zhí)行流,跳到該異常對應(yīng)的catch塊,由該catch塊來處理該異常。也就是說,不管是系統(tǒng)自動拋出的異常,還是程序員手動拋出的異常,Java運(yùn)行時環(huán)境對異常的處理沒有任何差別。
如果throw語句拋出的異常是Checked異常,則該throw語句要么處于try塊里,顯式捕獲該異常,要么放在一個帶throws聲明拋出的方法中,即把該異常交給該方法的調(diào)用者處理;如果throw語句拋出的異常是Runtime異常,則該語句無須放在try塊里,也無須放在帶throws聲明拋出的方法中;程序既可以顯式使用try...catch來捕獲并處理該異常,也可以完全不理會該異常,把該異常交給該方法調(diào)用者處理。拋出Checked異常則可以讓編譯器提醒程序員必須處理該異常。
自定義異常類
用戶自定義異常都應(yīng)該繼承Exception基類,如果希望自定義Runtime異常,則應(yīng)該繼承Runtime Exception基類。定義異常類時通常需要提供兩個構(gòu)造器:一個是無參數(shù)的構(gòu)造器,另一個是帶一個字符串參數(shù)的構(gòu)造器,這個字符串將作為該異常對象的描述信息(也就是異常對象的getMessage()方法的返回值)。
catch和throw同時使用
前面介紹的異常處理方式有如下兩種:1.在出現(xiàn)異常的方法內(nèi)捕獲并處理異常,該方法的調(diào)用者將不能再次捕獲該異常。2.該方法簽名中聲明拋出該異常,將該異常完全交給方法調(diào)用者處理。
在實際應(yīng)用中往往需要更復(fù)雜的處理方式,當(dāng)一個異常出現(xiàn)時,單靠某個方法無法完全處理該異常,必須由幾個方法協(xié)作才可完全處理該異常。也就是說,在異常出現(xiàn)的當(dāng)前方法中,程序只對異常進(jìn)行部分處理,還有些處理需要在該方法的調(diào)用者中才能完成,所以應(yīng)該再次拋出異常,讓該方法的調(diào)用者也能捕獲到異常。為了實現(xiàn)這種通過多個方法協(xié)作處理同一個異常的情形,可以在catch塊中結(jié)合throw語句來完成。
Java7增強(qiáng)的throw語句
異常鏈
對于一個真實的企業(yè)級應(yīng)用來說,當(dāng)業(yè)務(wù)邏輯層訪問數(shù)據(jù)持久層出現(xiàn)SQLException異常時,程序不應(yīng)該把底層的SQLException異常傳到用戶界面。通常的做法是:程序先捕獲原始異常,然后在catch塊中再throw一個新的業(yè)務(wù)異常,新的業(yè)務(wù)異常中包含了對用戶的提示信息,這種處理方式被稱為異常轉(zhuǎn)譯。
這種捕獲一個異常然后接著拋出另一個異常,并把原始異常信息保存下來是一種典型的鏈?zhǔn)教幚?23種設(shè)計模式之一:職責(zé)鏈模式),也被稱為"異常鏈"。
在JDK4以前,程序員必須自己編寫代碼來保持原始異常信息。從JDK4以后,所有Throwable的子類在構(gòu)造器中都可以接收一個cause對象作為參數(shù)。這個cause就用來表示原始異常,這樣可以把原始異常傳遞給新的異常,使得即使在當(dāng)前位置創(chuàng)建并拋出了新的異常,你也能通過這個異常鏈追蹤到異常最初發(fā)生的位置。例如希望通過SalException去追蹤到最原始的異常信息,則可以將該方法改寫為如下形式。
上面程序代碼創(chuàng)建SalException對象時,傳入了一個SQLException和Exception對象,而不是傳入了一個String對象,這就需要SalException類有相應(yīng)的構(gòu)造器。從JDK4以后,Throwable基類已有了一個可以接收Exception參數(shù)的方法,所以可以采用如下代碼來定義SalException類。
Java異常跟蹤棧
異常對象的printStackTrace()方法用于打印異常的跟蹤棧信息,根據(jù)printStackTrace()方法的輸出結(jié)果,開發(fā)者可以找到異常的源頭,并跟蹤異常觸發(fā)的過程。
面向?qū)ο蟮膽?yīng)用程序運(yùn)行時,經(jīng)常會發(fā)生一系列方法調(diào)用,從而形成"方法調(diào)用棧",異常的傳播則相反:只要異常沒有被完全捕獲(包括異常沒有被捕獲或異常被處理后重新拋出了新異常),異常從發(fā)生異常的方法逐漸向外傳播,首先傳給該方法的調(diào)用者,該方法調(diào)用者再次傳給其調(diào)用者…...直至最后傳到main方法,如果main方法依然沒有處理該異常,JVM會中止該程序,并打印異常的跟蹤棧信息。
一行行地往下看,跟蹤棧總是最內(nèi)部的被調(diào)用方法逐漸上傳直到最外部業(yè)務(wù)操作的起點(diǎn),通常就是程序的入口main方法或Thread類的run()方法(多線程的情形)。
異常傳播到Thread類的run方法就會結(jié)束(如果該異常沒有得到處理,將會導(dǎo)致該線程中止運(yùn)行)。
- 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
- 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
- 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
- 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
- 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
- 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
- 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
- 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
- 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...