java零基礎(chǔ)入門-高級(jí)特性篇(九) 異常 中
上一節(jié)講到了檢查異常,這種必須處理的異常到底該怎么處理呢?通常的處理方式就是捕獲異常或者拋出異常,捕獲異常就是在異常出現(xiàn)的時(shí)候當(dāng)場(chǎng)解決,而拋出異常則是把鍋甩出去,把異常往上層拋出,讓上層邏輯來(lái)解決它。處理異常有專門的關(guān)鍵字,java中的異常家族里有以下幾種關(guān)鍵字,try、catch、finally、throw、throws,下面來(lái)分別介紹它們。
捕獲異常
捕獲異常就是當(dāng)場(chǎng)就地正法,使用try和catch關(guān)鍵字來(lái)處理異常。try用來(lái)監(jiān)視代碼邏輯的運(yùn)行,如果沒(méi)有異常,那么程序會(huì)一直運(yùn)行到結(jié)束,而一旦發(fā)生異常,并且在try的監(jiān)控范圍之內(nèi),那么程序就會(huì)跳轉(zhuǎn)到catch部分,運(yùn)行catch里面的代碼。如果沒(méi)有捕獲異常,程序會(huì)直接結(jié)束,所以捕獲異??梢越o我們一次挽救程序異常停止的機(jī)會(huì),就算不能挽救,也至少可以知道為什么程序會(huì)出現(xiàn)異常。

上面這個(gè)try-catch結(jié)構(gòu)就是基本的捕獲異常結(jié)構(gòu),try后面的程序就是正常的邏輯代碼,catch后面是如果發(fā)生了異常需要執(zhí)行的代碼。需要注意的是,在出現(xiàn)異常以后,不會(huì)繼續(xù)執(zhí)行程序,而是直接跳到catch部分執(zhí)行代碼,所以這里輸出完第一個(gè)打印語(yǔ)句以后就馬上輸出了異常信息。
e是Exception的對(duì)象,調(diào)用Exception父類Throwable的方法printStackTrace(),輸出異常信息,輸出異常信息有多種方式,printStackTrace()是一種,這種方式輸出的是最詳細(xì)的錯(cuò)誤信息,包括出現(xiàn)異常代碼的行號(hào),異常信息和異常原因,這種方式適合調(diào)試程序,找到錯(cuò)誤。還有一種getMessage(),這種輸出只會(huì)輸出異常的信息,比上面一種方法輸出的信息要少,這種方式適合記錄日志,將錯(cuò)誤的信息作為日志記錄下來(lái),以便需要的時(shí)候排查問(wèn)題。
多個(gè)異常的捕獲結(jié)構(gòu)
上面的例子是使用Exception捕獲的異常,其實(shí)理論上來(lái)說(shuō),應(yīng)該使用最準(zhǔn)確的異常來(lái)捕獲,由于Exception是所有異常的父類,所以使用Exception沒(méi)有問(wèn)題,但是最適當(dāng)?shù)姆绞绞鞘褂肍ileNotFoundException來(lái)捕獲這里的異常。為什么要用子類來(lái)捕獲異常?因?yàn)槭褂米宇惒东@異??梢詫惓L幚淼母泳?xì),比如下面這個(gè)例子。(里面流和反射的知識(shí)可能沒(méi)有學(xué)到,但是此處只需關(guān)注異常即可)。

當(dāng)一段邏輯中有可能出現(xiàn)多個(gè)異常需要捕獲的時(shí)候,如果直接使用Exception,那么只能執(zhí)行一個(gè)異常邏輯,而不能將不同的異常區(qū)分開(kāi)。這樣就導(dǎo)致無(wú)法根據(jù)不同的異常進(jìn)行不同的異常處理。當(dāng)一段邏輯中出現(xiàn)多個(gè)需要捕獲的異常的時(shí)候,可以在try后面接多個(gè)catch,分別對(duì)不同的異常類型進(jìn)行捕獲。

Exception和Exception的子類在捕獲異常的時(shí)候是不沖突的,但是子類的捕獲必須在父類之前,如果第一個(gè)catch的是Exception,那么他會(huì)直接捕獲所有異常,不能單獨(dú)處理其他異常了。異常捕獲的順序是按照異常出現(xiàn)的順序來(lái)的,如果首先出現(xiàn)的是文件找不到異常,那么會(huì)被FileNotFoundException捕獲,如果首先出現(xiàn)的是ClassNotFoundException,那么會(huì)被Exception捕獲。
有finally的結(jié)構(gòu)
講完try和catch關(guān)鍵字以后,再來(lái)看另一個(gè)關(guān)鍵字finally。在處理異常的時(shí)候,try關(guān)鍵字是必須出現(xiàn)的,有了try關(guān)鍵字,程序才會(huì)在try所包含的代碼塊中捕獲異常,而catch和finally是可以任意出現(xiàn)一個(gè)的,也可以兩個(gè)同時(shí)出現(xiàn)。finally的特性是,不論在catch中是否出現(xiàn)異常,finally中的代碼都會(huì)被執(zhí)行。因?yàn)橛幸恍┐a如果寫(xiě)在try中,如果出現(xiàn)異常,那么這些代碼是可能不會(huì)被執(zhí)行的,如果寫(xiě)在catch中,如果不發(fā)生異常也不會(huì)執(zhí)行,所以需要一個(gè)地方來(lái)寫(xiě)無(wú)論是否出現(xiàn)異常都會(huì)被執(zhí)行的代碼。

finally在處理資源的時(shí)候非常有用,比如IO,網(wǎng)絡(luò),數(shù)據(jù)連接等等,因?yàn)樵谑褂眠@些資源的時(shí)候需要在代碼中手動(dòng)的回收,但是如果發(fā)生異常就不會(huì)執(zhí)行到回收資源的代碼,所以在finally中回收資源是一種很好的選擇。
使用finally需要注意的幾個(gè)地方:
1.如果有一個(gè)或多個(gè)catch關(guān)鍵字的話,finally要出現(xiàn)在最后一個(gè)catch之后。順序如果有錯(cuò)誤會(huì)發(fā)生編譯錯(cuò)誤。
2.不建議在finally里面使用return關(guān)鍵字。

在finally里面是可以使用return關(guān)鍵字的,但是會(huì)導(dǎo)致結(jié)果與預(yù)期不符合。比如上面這個(gè)例子,上例中其實(shí)是不檢查異常,可以捕獲也可以不捕獲,這里為了說(shuō)明finally就捕獲異常了。這里預(yù)期返回的是兩個(gè)參數(shù)的商,程序運(yùn)行到try中的return是不會(huì)馬上結(jié)束方法的,因?yàn)楹竺嬗衒inally語(yǔ)句,而finally語(yǔ)句中也有return,最后的結(jié)果就是finally中的return導(dǎo)致try中的return無(wú)效。無(wú)論程序是否發(fā)生異常,方法預(yù)期返回的結(jié)果都被改變了,返回的不是程序希望得到的兩個(gè)參數(shù)的商,而是一個(gè)與參數(shù)無(wú)關(guān)的字符串,所以通常不建議在finally中使用return關(guān)鍵字。
final ,finally 和finalize
這個(gè)地方要指出的是,這幾個(gè)關(guān)鍵字八竿子打不著關(guān)系,但是經(jīng)常會(huì)有外行題目問(wèn)這幾個(gè)關(guān)鍵字有什么區(qū)別。這里簡(jiǎn)單說(shuō)一下。
final 定義的變量,初始化變量后不可修改。final定義的方法不可以被覆寫(xiě)。final定義的類不可以繼承。
finally用于異常結(jié)構(gòu),不論是否發(fā)生異常,都會(huì)運(yùn)行finally中的代碼。
finalize用于定義垃圾回收器應(yīng)該執(zhí)行的操作。
拋出異常
捕獲異常講完了,輪到拋出異常了。前面說(shuō)了檢查異常,有沒(méi)有想過(guò),為什么檢查異常就必須處理呢?因?yàn)樵诙x類,方法的時(shí)候,源碼已經(jīng)將異常拋出了,所以你在使用類的時(shí)候就必須處理它,要么捕獲,要么拋出。前面例子中
FileOutputStream out = new FileOutputStream(file);
會(huì)有一個(gè)FileNotFoundException類型的異常必須處理,來(lái)看看FileOutputStream這個(gè)類的構(gòu)造器。

什么是拋出異常?
拋出異常就是遇到檢查異常,并沒(méi)有捕獲異常直接處理,而是將異常交給調(diào)用方處理。
為什么要拋出異常而不是直接捕獲?
因?yàn)樵O(shè)計(jì)上的需要。當(dāng)我們?cè)趯?xiě)一個(gè)業(yè)務(wù)的時(shí)候,碰見(jiàn)異常最好的方法就是捕獲并處理它。但是如果寫(xiě)的是一個(gè)公共的工具方法或者是父類,抽象類等需要將業(yè)務(wù)進(jìn)行抽象的時(shí)候,并不能預(yù)見(jiàn)到具體的業(yè)務(wù)是什么,所以不能直接給出解決方案,這時(shí)候就需要將異常交給調(diào)用方,在使用者具體使用的時(shí)候,再來(lái)捕獲該異常,根據(jù)具體情況確定具體的處理方式。
異常具體是怎么拋出的?

首先在一個(gè)需要拋出異常的地方將異常往上一級(jí)(方法的調(diào)用者)拋出,然后上一級(jí)還可以繼續(xù)往上一級(jí)拋出,如果到最后都沒(méi)有被捕獲,該異常會(huì)被拋給jvm,jvm也沒(méi)法處理異常只能把異常信息打印出來(lái)。
這個(gè)過(guò)程就像出了問(wèn)題,開(kāi)始甩鍋一樣。方法A出了問(wèn)題,自己可能沒(méi)有辦法處理,就把鍋甩給了方法B,方法B一看這個(gè)我也沒(méi)法解決啊,轉(zhuǎn)手又甩了出去,最后這個(gè)鍋被甩給了老大哥JVM,JVM老大哥看到異常也只能干瞪眼,沒(méi)有辦法最后只能把異常信息打印出來(lái),誰(shuí)寫(xiě)的代碼誰(shuí)來(lái)認(rèn)領(lǐng)一下,錯(cuò)誤給你看了,自己想辦法去解決。

java使用關(guān)鍵字 throws 拋出異常,throws后面跟上異常的類型,跟catch的捕獲類型差不多,定義什么類型的異常就會(huì)拋出什么類型的異常,如果直接拋出Exception,那么就是拋出所有的異常類型。跟catch可以捕獲多種異常類型一樣,throws也可以拋出多種異常類型,這樣就可以讓上一級(jí)的代碼根據(jù)不同的異常類型分別進(jìn)行處理。如果只拋出Exception類型的異常,上一級(jí)就無(wú)法對(duì)異常進(jìn)行精確的控制了。

使用throws同時(shí)拋出多個(gè)異常的時(shí)候,使用逗號(hào)將多個(gè)異常分開(kāi)。throws這種拋出異常的方式可以看做是一種被動(dòng)式拋異常,因?yàn)閠hrows拋出的異??赡馨l(fā)生也可能不發(fā)生,java中除了throws拋出異常,還有一種主動(dòng)式拋異常,下面來(lái)看看什么是主動(dòng)式拋異常。
throws 和 throw
主動(dòng)拋出異常的關(guān)鍵字是throw。和throws只差了一個(gè)小寫(xiě)字母s,這里需要重點(diǎn)區(qū)分開(kāi)兩種異常拋出方式的區(qū)別。
throws:1)拋出的是類,在方法后面寫(xiě)的是異常的類名? 2)可以同時(shí)拋出多種類型異常? 3)throws拋出的異常不一定會(huì)發(fā)生 4)在方法名處拋出
throw:1)拋出的是異常類的實(shí)例? 2)只能拋出一種異常 3)拋出的異常一定會(huì)發(fā)生 4)在方法內(nèi)部拋出
throw用在拋出不檢查異常的情況比較多。使用throw可以將代碼的邏輯補(bǔ)充的更加完整,因?yàn)槟承┊惓T谔囟ǖ那闆r是需要根據(jù)業(yè)務(wù)邏輯來(lái)判斷是否拋出,在特定的情況下是可以確定異常的,而不是像throws不確定是否會(huì)出現(xiàn)異常。這種情況下就可以使用throw在方法體中拋出異常。

上例中,假設(shè)用戶需要輸入兩個(gè)數(shù)字,然后計(jì)算兩個(gè)數(shù)字的商。用戶輸入是不確定的,但是一旦用戶將intTest2輸入為0,代碼邏輯可以確定這里肯定會(huì)有一個(gè)異常,那么可以直接使用throw來(lái)拋出這個(gè)異常。一旦在調(diào)用方法時(shí)捕獲到該異常,也可以確定異常的信息,比如上例中可以將捕獲到的信息直接反饋給用戶,第二個(gè)數(shù)不能為0。
需要注意的是throw只是拋出異常的方式比較靈活,可以在代碼邏輯中拋出異常,而拋出異常以后,上級(jí)的處理邏輯和throws是一樣的,要么繼續(xù)往上級(jí)拋異常,要么捕獲異常。