異常分類
Java將異常分為兩種流酬,Checked異常和Runtime異常旨怠。Java認(rèn)為Checked異常都是可以在編譯階段被處理的異常任岸,所以它強(qiáng)制程序處理所有的Checked異常渤闷;而Runtime異常則無須處理惶桐。
try-catch塊
如果執(zhí)行try塊里的業(yè)務(wù)邏輯代碼時(shí)出現(xiàn)異常,系統(tǒng)自動(dòng)生成一個(gè)異常對(duì)象淀歇,該異常對(duì)象被提交給Java運(yùn)行時(shí)環(huán)境易核,這個(gè)過程被稱為拋出異常,同時(shí)try塊后續(xù)代碼通常將得不到執(zhí)行浪默。當(dāng)Java運(yùn)行時(shí)環(huán)境收到異常對(duì)象時(shí)牡直,會(huì)尋找能處理該異常對(duì)象的catch塊,如果找到合適的catch塊纳决,則把該異常對(duì)象交給該catch塊處理碰逸,這個(gè)過程被稱為捕獲異常:如果Java運(yùn)行時(shí)環(huán)境找不到捕獲異常的catch塊,則運(yùn)行時(shí)環(huán)境終止阔加,Java程序也將退出饵史。
注意不管程序代碼塊是否處于try塊中,甚至包括catch塊中的代碼,只要執(zhí)行該代碼塊時(shí)出現(xiàn)了異常胳喷,系統(tǒng)總會(huì)自動(dòng)生成一個(gè)異常對(duì)象湃番。如果程序沒有為這段代碼定義任何的catch塊,則Java運(yùn)行時(shí)環(huán)境無法找到處理該異常的catch塊厌蔽,程序就在此退出牵辣。
當(dāng)Java運(yùn)行時(shí)環(huán)境接收到異常對(duì)象后摔癣,會(huì)依次判斷該異常對(duì)象是否是catch塊后異常類或其子類的實(shí)例奴饮,如果是,Java運(yùn)行時(shí)環(huán)境將調(diào)用該catch塊來處理該異常择浊,否則再次拿該異常對(duì)象和下一個(gè)catch塊里的異常類進(jìn)行比較戴卜。try塊后可以有多個(gè)catch塊,這是為了針對(duì)不同的異常類提供不同的異常處理方式琢岩。當(dāng)系統(tǒng)發(fā)生不同的意外情況時(shí)投剥,系統(tǒng)會(huì)生成不同的異常對(duì)象,Java運(yùn)行時(shí)就會(huì)根據(jù)該異常對(duì)象所屬的異常類來決定使用哪個(gè)catch塊來處理該異常担孔。
注意:try塊里聲明的變量是代碼塊內(nèi)局部變量江锨,它只在try塊內(nèi)有效,在catch塊中不能訪問該變量糕篇。
異常類的繼承體系
Java提供了豐富的異常類啄育,這些異常類之間有嚴(yán)格的繼承關(guān)系。
Java把所有的非正常情況分成兩種:異常和錯(cuò)誤拌消,它們都繼承Throwable父類挑豌。Error錯(cuò)誤一般是指與虛擬機(jī)相關(guān)的問題,如系統(tǒng)崩潰墩崩、虛擬機(jī)錯(cuò)誤氓英、動(dòng)態(tài)鏈接失敗等,這種錯(cuò)誤無法恢復(fù)或不可能捕獲鹦筹,將導(dǎo)致應(yīng)用程序中斷铝阐。通常應(yīng)用程序無法處理這些錯(cuò)誤,因此應(yīng)用程序不應(yīng)該試圖使用catch塊來捕獲Error對(duì)象铐拐。在定義該方法時(shí)饰迹,也無須在其throws子句中聲明該方法可能拋出Error異常及其任何子類。
進(jìn)行異常捕獲時(shí)不僅應(yīng)該把Exception類對(duì)應(yīng)的catch塊放在最后余舶,而且所有父類異常的catch塊都應(yīng)該排在子類異常catch塊的后面啊鸭,否則將出現(xiàn)編譯錯(cuò)誤。
Java7提供的多異常捕獲
在Java7以前匿值,每個(gè)?catch塊只能捕獲一種類型的異常赠制,但從Java7開始,一個(gè)catch塊可以捕獲多種類型的異常。使用一個(gè)catch塊捕獲多種類型的異常時(shí)需要注意如下兩個(gè)地方:1.捕獲多種類型的異常時(shí)钟些,多種異常類型之間用豎線|隔開烟号。2.捕獲多種類型的異常時(shí),異常變量有隱式的final修飾政恍,因此程序不能對(duì)異常變量重新賦值汪拥。
使用finally回收資源
有些時(shí)候,程序在try塊里打開了一些物理資源(例如數(shù)據(jù)庫(kù)連接篙耗、網(wǎng)絡(luò)連接和磁盤文件等)迫筑,這些物理資源都必須顯式回收。Java的垃圾回收機(jī)制不會(huì)回收任何物理資源宗弯,垃圾回收機(jī)制只能回收堆內(nèi)存中對(duì)象所占用的內(nèi)存脯燃。為了保證一定能回收try塊中打開的物理資源,異常處理機(jī)制提供了finally塊蒙保。不管try塊中的代碼是否出現(xiàn)異常辕棚,也不管哪一個(gè)catch塊被執(zhí)行,甚至在try塊或catch塊中執(zhí)行了return語(yǔ)句邓厕,finally塊總會(huì)被執(zhí)行(程序?qū)⒁\(yùn)行到return語(yǔ)句之前逝嚎,會(huì)跳轉(zhuǎn)到finally塊中執(zhí)行,執(zhí)行完畢后會(huì)再執(zhí)行之前的return語(yǔ)句)详恼。在通常情況下补君,一旦在方法里執(zhí)行到return語(yǔ)句的地方,程序?qū)⒘⒓唇Y(jié)束該方法单雾,現(xiàn)在不會(huì)了赚哗,雖然return語(yǔ)句也強(qiáng)制方法結(jié)束,但一定會(huì)先執(zhí)行finally塊里的代碼硅堆,然后再執(zhí)行前面的return語(yǔ)句屿储。但是如果在異常處理的catch塊或者try塊中執(zhí)行System.exit(1)語(yǔ)句來退出虛擬機(jī),finally塊將失去執(zhí)行的機(jī)會(huì)渐逃。
異常處理語(yǔ)法結(jié)構(gòu)中只有try塊是必需的够掠,catch塊和finally塊都是可選的,但catch塊和finally塊至少出現(xiàn)其中之一茄菊。
當(dāng)Java程序執(zhí)行try塊或catch塊時(shí)遇到了return或throw語(yǔ)句時(shí)疯潭,這兩個(gè)語(yǔ)句本都會(huì)導(dǎo)致該方法立即結(jié)束,但是系統(tǒng)并不會(huì)立即執(zhí)行這兩個(gè)語(yǔ)句面殖,而是去尋找該異常處理流程中是否包含finally塊竖哩,如果沒有finally塊,程序立即執(zhí)行return或throw語(yǔ)句脊僚,方法終止相叁;如果有finally塊,系統(tǒng)立即開始執(zhí)行finally塊,只有當(dāng)finally塊執(zhí)行完成后增淹,系統(tǒng)才會(huì)再次跳回來執(zhí)行try塊或catch塊里的return或throw語(yǔ)句椿访;如果finally塊里也使用了return或throw等導(dǎo)致方法終止的語(yǔ)句,finally塊已經(jīng)終止了方法虑润,系統(tǒng)將不會(huì)跳回去執(zhí)行try塊或catch塊里的任何代碼成玫。
Java7的自動(dòng)關(guān)閉資源的try語(yǔ)句
當(dāng)程序使用finally塊關(guān)閉資源時(shí),程序顯得異常臃腫拳喻。
Java7增強(qiáng)了try語(yǔ)句的功能哭当,它允許在try關(guān)鍵字后緊跟一對(duì)圓括號(hào),圓括號(hào)中可以聲明和初始化一個(gè)或多個(gè)資源舞蔽。此處的資源指的是那些必須在程序結(jié)束時(shí)顯式關(guān)閉的資源(比如數(shù)據(jù)庫(kù)連接或網(wǎng)絡(luò)連接等)荣病,try語(yǔ)句在自身結(jié)束時(shí)自動(dòng)關(guān)閉這些資源码撰。
需要指出的是渗柿,為了保證try語(yǔ)句可以正常關(guān)閉資源,這些資源實(shí)現(xiàn)類必須實(shí)現(xiàn)AutoCloseable或Closeable接口脖岛,實(shí)現(xiàn)這兩個(gè)接口就必須實(shí)現(xiàn)close()方法朵栖。Closeable是AutoCloseable的子接口,可以被自動(dòng)關(guān)閉的資源類要么實(shí)現(xiàn)AutoCloseable接口柴梆,要么實(shí)現(xiàn)Closeable接口陨溅。Closeable接口里的close()方法聲明拋出了IOException,因此它的實(shí)現(xiàn)類在實(shí)現(xiàn)close()方法時(shí)只能聲明拋出IOException或其子類绍在;AutoCloseable接口里的close()方法聲明拋出了Exception门扇,因此它的實(shí)現(xiàn)類在實(shí)現(xiàn)close()方法時(shí)可以聲明拋出任何異常。
自動(dòng)關(guān)閉資源的try語(yǔ)句相當(dāng)于包含了隱式的finally塊(這個(gè)finally塊用于關(guān)閉資源)偿渡,因此這個(gè)try語(yǔ)句可以既沒有catch塊也沒有finally塊臼寄。如果程序需要,自動(dòng)關(guān)閉資源的try語(yǔ)句后也可以帶多個(gè)catch塊和一個(gè)finally塊溜宽。
~Checked異常和Runtime異常
Java的異常被分為兩大類:Checked異常和Runtime異常吉拳。
Java認(rèn)為Checked異常都是可以被處理的異常,所以Java程序必須顯式處理Checked異常适揉。如果程序沒有處理Checked異常留攒,將無法通過編譯。對(duì)于Checked異常的處理方式有如下兩種:1.當(dāng)前方法明確知道如何處理該異常嫉嘀,程序應(yīng)該使用try...catch塊來捕獲該異常炼邀,然后在catch塊中處理該異常。2.當(dāng)前方法不知道如何處理這種異常剪侮,應(yīng)該在定義該方法時(shí)聲明拋出異常拭宁。即對(duì)于Checked異常,要么顯式聲明拋出,要么顯式捕獲并處理它红淡。
Runtime異常則更加靈活不狮,Runtime異常無須顯式聲明拋出,如果程序需要捕獲Runtime異常在旱,也可以使用try...catch塊來實(shí)現(xiàn)摇零。
使用throws聲明拋出異常
使用throws聲明拋出異常的思路是,當(dāng)前方法不知道如何處理這種類型的異常桶蝎,該異常應(yīng)該由上級(jí)調(diào)用者處理驻仅;如果main方法也不知道如何處理這種類型的異常,也可以使用throws聲明拋出異常登渣,該異常將交給JVM處理噪服。JVM對(duì)異常的處理方法是,打印異常的跟蹤棧信息胜茧,并中止程序運(yùn)行粘优,這就是前面程序在遇到異常后自動(dòng)結(jié)束的原因。
throws聲明拋出的語(yǔ)法格式如下:throws ExceptionClass1,ExceptionClass2...
一旦使用throws語(yǔ)句聲明拋出該異常呻顽,程序就無須使用try...catch塊來捕獲該異常了雹顺。如果某段代碼中調(diào)用了一個(gè)帶throws聲明的方法,該方法聲明拋出了Checked異常廊遍,則表明該方法希望它的調(diào)用者來處理該異常嬉愧。也就是說調(diào)用該方法時(shí)要么放在try塊中顯式捕獲該異常,要么放在另一個(gè)帶throws聲明拋出的方法中喉前。
使用throws聲明拋出異常時(shí)有一個(gè)限制没酣,就是方法重寫時(shí)的一條規(guī)則:子類方法(重寫了父類的方法)聲明拋出的異常類型應(yīng)該是父類方法聲明拋出的異常類型的子類或相同,子類方法(重寫了父類的方法)聲明拋出的異常不允許比父類方法聲明拋出的異常多卵迂。
使用Checked異常至少存在如下兩大不便之處:1.對(duì)于程序中的Checked異常裕便,Java要求必須顯式捕獲并處理該異常或者顯式聲明拋出該異常狭握,這樣就增加了編程復(fù)雜度闪金。2.如果在方法中顯式聲明拋出Checked異常,將會(huì)導(dǎo)致方法簽名與異常耦合论颅,如果該方法是重寫父類的方法哎垦,則該方法拋出的異常還會(huì)受到被重寫方法所拋出異常的限制。
在大部分時(shí)候推薦使用Runtime異常恃疯,而不使用Checked異常漏设。尤其當(dāng)程序需要自行拋出異常時(shí),使用Runtime異常將更加簡(jiǎn)潔今妄。當(dāng)使用Runtime異常時(shí)郑口,程序無須在方法中聲明拋出Checked異常鸳碧,一旦發(fā)生了自定義錯(cuò)誤,程序只管拋出Runtime異常即可犬性。如果程序需要在合適的地方捕獲異常并對(duì)異常進(jìn)行處理瞻离,則一樣可以使用try…catch塊來捕獲Runtime異常。
使用throw拋出異常
當(dāng)程序出現(xiàn)錯(cuò)誤時(shí)乒裆,系統(tǒng)會(huì)自動(dòng)拋出異常套利;除此之外,Java也允許程序自行拋出異常鹤耍,自行拋出異常使用throw語(yǔ)句來完成肉迫。 throw語(yǔ)句拋出的是一個(gè)異常實(shí)例,而且每次只能拋出一個(gè)異常實(shí)例稿黄。當(dāng)Java運(yùn)行時(shí)接收到開發(fā)者自行拋出的異常時(shí)喊衫,同樣會(huì)中止當(dāng)前的執(zhí)行流,跳到該異常對(duì)應(yīng)的catch塊杆怕,由該catch塊來處理該異常族购。也就是說,不管是系統(tǒng)自動(dòng)拋出的異常财著,還是程序員手動(dòng)拋出的異常联四,Java運(yùn)行時(shí)環(huán)境對(duì)異常的處理沒有任何差別撑碴。
如果throw語(yǔ)句拋出的異常是Checked異常撑教,則該throw語(yǔ)句要么處于try塊里,顯式捕獲該異常醉拓,要么放在一個(gè)帶throws聲明拋出的方法中伟姐,即把該異常交給該方法的調(diào)用者處理;如果throw語(yǔ)句拋出的異常是Runtime異常亿卤,則該語(yǔ)句無須放在try塊里愤兵,也無須放在帶throws聲明拋出的方法中;程序既可以顯式使用try...catch來捕獲并處理該異常排吴,也可以完全不理會(huì)該異常秆乳,把該異常交給該方法調(diào)用者處理。拋出Checked異常則可以讓編譯器提醒程序員必須處理該異常钻哩。
自定義異常類
用戶自定義異常都應(yīng)該繼承Exception基類屹堰,如果希望自定義Runtime異常,則應(yīng)該繼承Runtime Exception基類街氢。定義異常類時(shí)通常需要提供兩個(gè)構(gòu)造器:一個(gè)是無參數(shù)的構(gòu)造器扯键,另一個(gè)是帶一個(gè)字符串參數(shù)的構(gòu)造器,這個(gè)字符串將作為該異常對(duì)象的描述信息(也就是異常對(duì)象的getMessage()方法的返回值)珊肃。
catch和throw同時(shí)使用
前面介紹的異常處理方式有如下兩種:1.在出現(xiàn)異常的方法內(nèi)捕獲并處理異常荣刑,該方法的調(diào)用者將不能再次捕獲該異常馅笙。2.該方法簽名中聲明拋出該異常,將該異常完全交給方法調(diào)用者處理厉亏。
在實(shí)際應(yīng)用中往往需要更復(fù)雜的處理方式董习,當(dāng)一個(gè)異常出現(xiàn)時(shí),單靠某個(gè)方法無法完全處理該異常爱只,必須由幾個(gè)方法協(xié)作才可完全處理該異常阱飘。也就是說,在異常出現(xiàn)的當(dāng)前方法中虱颗,程序只對(duì)異常進(jìn)行部分處理沥匈,還有些處理需要在該方法的調(diào)用者中才能完成,所以應(yīng)該再次拋出異常忘渔,讓該方法的調(diào)用者也能捕獲到異常高帖。為了實(shí)現(xiàn)這種通過多個(gè)方法協(xié)作處理同一個(gè)異常的情形,可以在catch塊中結(jié)合throw語(yǔ)句來完成畦粮。
Java7增強(qiáng)的throw語(yǔ)句
異常鏈
對(duì)于一個(gè)真實(shí)的企業(yè)級(jí)應(yīng)用來說散址,當(dāng)業(yè)務(wù)邏輯層訪問數(shù)據(jù)持久層出現(xiàn)SQLException異常時(shí),程序不應(yīng)該把底層的SQLException異常傳到用戶界面宣赔。通常的做法是:程序先捕獲原始異常预麸,然后在catch塊中再throw一個(gè)新的業(yè)務(wù)異常,新的業(yè)務(wù)異常中包含了對(duì)用戶的提示信息儒将,這種處理方式被稱為異常轉(zhuǎn)譯吏祸。
這種捕獲一個(gè)異常然后接著拋出另一個(gè)異常,并把原始異常信息保存下來是一種典型的鏈?zhǔn)教幚?23種設(shè)計(jì)模式之一:職責(zé)鏈模式)钩蚊,也被稱為"異常鏈"贡翘。
在JDK4以前,程序員必須自己編寫代碼來保持原始異常信息砰逻。從JDK4以后鸣驱,所有Throwable的子類在構(gòu)造器中都可以接收一個(gè)cause對(duì)象作為參數(shù)。這個(gè)cause就用來表示原始異常蝠咆,這樣可以把原始異常傳遞給新的異常踊东,使得即使在當(dāng)前位置創(chuàng)建并拋出了新的異常,你也能通過這個(gè)異常鏈追蹤到異常最初發(fā)生的位置刚操。例如希望通過SalException去追蹤到最原始的異常信息闸翅,則可以將該方法改寫為如下形式。
上面程序代碼創(chuàng)建SalException對(duì)象時(shí)赡茸,傳入了一個(gè)SQLException和Exception對(duì)象缎脾,而不是傳入了一個(gè)String對(duì)象,這就需要SalException類有相應(yīng)的構(gòu)造器占卧。從JDK4以后遗菠,Throwable基類已有了一個(gè)可以接收Exception參數(shù)的方法联喘,所以可以采用如下代碼來定義SalException類。
Java異常跟蹤棧
異常對(duì)象的printStackTrace()方法用于打印異常的跟蹤棧信息辙纬,根據(jù)printStackTrace()方法的輸出結(jié)果豁遭,開發(fā)者可以找到異常的源頭,并跟蹤異常觸發(fā)的過程贺拣。
面向?qū)ο蟮膽?yīng)用程序運(yùn)行時(shí)蓖谢,經(jīng)常會(huì)發(fā)生一系列方法調(diào)用,從而形成"方法調(diào)用棧"譬涡,異常的傳播則相反:只要異常沒有被完全捕獲(包括異常沒有被捕獲或異常被處理后重新拋出了新異常)闪幽,異常從發(fā)生異常的方法逐漸向外傳播,首先傳給該方法的調(diào)用者涡匀,該方法調(diào)用者再次傳給其調(diào)用者…...直至最后傳到main方法盯腌,如果main方法依然沒有處理該異常,JVM會(huì)中止該程序陨瘩,并打印異常的跟蹤棧信息腕够。
一行行地往下看,跟蹤椛嗬停總是最內(nèi)部的被調(diào)用方法逐漸上傳直到最外部業(yè)務(wù)操作的起點(diǎn)帚湘,通常就是程序的入口main方法或Thread類的run()方法(多線程的情形)。
異常傳播到Thread類的run方法就會(huì)結(jié)束(如果該異常沒有得到處理甚淡,將會(huì)導(dǎo)致該線程中止運(yùn)行)大诸。
- 文/潘曉璐 我一進(jìn)店門沸手,熙熙樓的掌柜王于貴愁眉苦臉地迎上來外遇,“玉大人,你說我怎么就攤上這事契吉√拢” “怎么了?”我有些...
- 文/不壞的土叔 我叫張陵捐晶,是天一觀的道長(zhǎng)菲语。 經(jīng)常有香客問我妄辩,道長(zhǎng),這世上最難降的妖魔是什么山上? 我笑而不...
- 正文 為了忘掉前任眼耀,我火速辦了婚禮,結(jié)果婚禮上佩憾,老公的妹妹穿的比我還像新娘哮伟。我一直安慰自己,他們只是感情好妄帘,可當(dāng)我...
- 文/花漫 我一把揭開白布楞黄。 她就那樣靜靜地躺著,像睡著了一般抡驼。 火紅的嫁衣襯著肌膚如雪谅辣。 梳的紋絲不亂的頭發(fā)上,一...
- 文/蒼蘭香墨 我猛地睜開眼萎河,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蕉饼?” 一聲冷哼從身側(cè)響起虐杯,我...
- 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昧港,沒想到半個(gè)月后擎椰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
- 正文 獨(dú)居荒郊野嶺守林人離奇死亡创肥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
- 正文 我和宋清朗相戀三年达舒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叹侄。...
- 正文 年R本政府宣布撒强,位于F島的核電站禽捆,受9級(jí)特大地震影響笙什,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜睦擂,卻給世界環(huán)境...
- 文/蒙蒙 一得湘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧顿仇,春花似錦淘正、人聲如沸。這莊子的主人今日做“春日...
- 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至述呐,卻和暖如春惩淳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乓搬。 一陣腳步聲響...
- 正文 我出身青樓激蹲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親江掩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子学辱,可洞房花燭夜當(dāng)晚...