摘要:下面是異常處理機(jī)制的語(yǔ)法結(jié)構(gòu)業(yè)務(wù)實(shí)現(xiàn)代碼輸入不合法如果執(zhí)行塊里業(yè)務(wù)邏輯代碼時(shí)出現(xiàn)異常,系統(tǒng)自動(dòng)生成一個(gè)異常對(duì)象,該對(duì)象被提交給運(yùn)行時(shí)環(huán)境,這個(gè)過(guò)程被稱為拋出異常。
Java的異常機(jī)制主要依賴于try、catch、finally、throw和throws五個(gè)關(guān)鍵字,
try關(guān)鍵字后緊跟一個(gè)花括號(hào)括起來(lái)的代碼塊(花括號(hào)不可省略),簡(jiǎn)稱try塊,它里面放置可能引發(fā)異常的代碼
catch后對(duì)應(yīng)異常類型和一個(gè)代碼塊,用于表明該catch塊用于處理這種類型的代碼塊
多個(gè)catch塊后還可以跟一個(gè)finally塊,finally塊用于回收在try塊里打開(kāi)的物理資源,異常機(jī)制會(huì)保證finally塊總被執(zhí)行
throws關(guān)鍵字主要在方法簽名中使用,拋出一個(gè)具體的異常對(duì)象
throw用于拋出一個(gè)實(shí)際的異常,throw可以多帶帶作為語(yǔ)句使用,拋出一個(gè)具體的異常對(duì)象
我們希望所有的錯(cuò)誤都可以在編譯階段被發(fā)現(xiàn),就是在試圖運(yùn)行程序之前排除所有錯(cuò)誤,但這是不現(xiàn)實(shí)的,余下的問(wèn)題必須在運(yùn)行期間得到解決。JAVA將異常分為兩種,Checked異常和Runtime異常,JAVA認(rèn)為Checked異常都是可以在編譯階段被處理的異常,所以他強(qiáng)制程序處理所有多的Checked異常;而Runtime異常則無(wú)需處理
異常機(jī)制可以使程序中的異常處理代碼和正常業(yè)務(wù)代碼分離,保證程序代碼更加優(yōu)雅,并可以提高程序的健壯性
Java異常機(jī)制Java的異常處理機(jī)制可以讓程序具有極好的容錯(cuò)性,讓程序更加健壯。當(dāng)程序運(yùn)行出現(xiàn)意外情形時(shí),系統(tǒng)會(huì)自動(dòng)生成一個(gè)Exception對(duì)象來(lái)通知程序,從而實(shí)現(xiàn)將“業(yè)務(wù)功能實(shí)現(xiàn)代碼”和“錯(cuò)誤處理代碼”分離,提供更好的可讀性
使用try...catch捕獲異常Java提出了一種假設(shè):如果程序可以順利完成,那就“一切正?!?,把系統(tǒng)的業(yè)務(wù)實(shí)現(xiàn)代碼放在try塊中定義,所有的異常處理邏輯放在catch塊中進(jìn)行處理。下面是Java異常處理機(jī)制的語(yǔ)法結(jié)構(gòu)
try { // 業(yè)務(wù)實(shí)現(xiàn)代碼 ... } catch (Exception) { alert 輸入不合法 goto retry }
如果執(zhí)行try塊里業(yè)務(wù)邏輯代碼時(shí)出現(xiàn)異常,系統(tǒng)自動(dòng)生成一個(gè)異常對(duì)象,該對(duì)象被提交給Java運(yùn)行時(shí)環(huán)境,這個(gè)過(guò)程被稱為拋出(throw)異常。當(dāng)Java運(yùn)行時(shí)環(huán)境收到異常對(duì)象時(shí),會(huì)尋找能處理該異常的catch塊,如果找到合適的catch塊,則把該異常對(duì)象交給catch塊處理,這個(gè)過(guò)程被稱為捕獲(catch)異常;如果Java運(yùn)行時(shí)找不到捕獲異常的catch塊,則運(yùn)行時(shí)環(huán)境終止,Java程序也將退出
String inputStr = null; // br.readLine():每當(dāng)在鍵盤(pán)上輸入一行內(nèi)容按回車, // 用戶剛剛輸入的內(nèi)容將被br讀取到。 while ((inputStr = br.readLine()) != null) { try { // 將用戶輸入的字符串以逗號(hào)作為分隔符,分解成2個(gè)字符串 String[] posStrArr = inputStr.split(","); // 將2個(gè)字符串轉(zhuǎn)換成用戶下棋的坐標(biāo) int xPos = Integer.parseInt(posStrArr[0]); int yPos = Integer.parseInt(posStrArr[1]); // 把對(duì)應(yīng)的數(shù)組元素賦為"●"。 if (!gb.board[xPos - 1][yPos - 1].equals("╋")) { System.out.println("您輸入的坐標(biāo)點(diǎn)已有棋子了," + "請(qǐng)重新輸入"); continue; } gb.board[xPos - 1][yPos - 1] = "●"; } catch (Exception e) { System.out.println("您輸入的坐標(biāo)不合法,請(qǐng)重新輸入," + "下棋坐標(biāo)應(yīng)以x,y的格式"); continue; } ... }異常類的繼承體系
當(dāng)Java運(yùn)行時(shí)環(huán)境接收到異常對(duì)象時(shí),catch關(guān)鍵字形式(Exception e)的每一個(gè)catch塊都會(huì)處理該異常類及其實(shí)例
當(dāng)Java運(yùn)行時(shí)環(huán)境接收到異常對(duì)象后,會(huì)依次判斷該異常對(duì)象是否是catch塊后異常類或其子類的實(shí)例,如果是,Java運(yùn)行時(shí)環(huán)境將調(diào)用該catch塊來(lái)處理該異常;否則再次判斷該異常對(duì)象和下一個(gè)catch塊里的異常類進(jìn)行比較
Java異常捕獲流程示意圖
當(dāng)程序進(jìn)入負(fù)責(zé)異常處理的catch塊時(shí),系統(tǒng)生成的異常對(duì)象ex將會(huì)傳給catch塊后的異常形參,從而允許catch塊通過(guò)該對(duì)象來(lái)獲得異常的詳細(xì)信息
try塊后可以有多個(gè)catch塊,try塊后使用多個(gè)catch塊是為了針對(duì)不同異常類提供不同的異常處理方式。當(dāng)系統(tǒng)發(fā)生不同的意外情況時(shí),系統(tǒng)會(huì)生成不同的異常對(duì)象,Java運(yùn)行時(shí)就會(huì)根據(jù)該異常對(duì)象所屬的異常類來(lái)決定使用哪個(gè)catch塊來(lái)處理該異常
通過(guò)在try塊后提供多個(gè)catch塊可以無(wú)須在異常處理塊中使用if、switch判斷異常類型,但依然可以針對(duì)不同異常類型提供相應(yīng)的處理邏輯,從而提供更細(xì)致,更有調(diào)理的異常處理邏輯
從上圖可以看出,通常情況下,如果try塊被執(zhí)行一次,則try塊后只有一個(gè)catch塊會(huì)被執(zhí)行,絕不可能有多個(gè)catch塊被執(zhí)行,除非在循環(huán)中使用了continue開(kāi)始下一次循環(huán),下一次循環(huán)又重新運(yùn)行了try塊,這才可能導(dǎo)致多個(gè)catch塊被執(zhí)行
try塊與if語(yǔ)句不一樣,try塊后的花括號(hào)({...})不可以省略,即使try塊里只有一行代碼,也不可以省略這個(gè)花括號(hào)。與之類似的,catch塊后的花括號(hào)({...})也不可以省略。還有一點(diǎn)需要指出:try塊里聲明的變量是代碼塊內(nèi)局部變量,它只在try塊內(nèi)有效,catch塊中不能訪問(wèn)該變量。
Java常見(jiàn)的異常類之間的繼承關(guān)系圖
Java把所有非正常情況分成兩種:異常(Exception)和錯(cuò)誤(Error),它們都是繼承Throwable父類
Error錯(cuò)誤,一般是指與虛擬機(jī)(JVM)相關(guān)的問(wèn)題,如系統(tǒng)崩潰、虛擬機(jī)出錯(cuò)誤、動(dòng)態(tài)鏈接失敗等,這種錯(cuò)誤是java程序的根本運(yùn)行環(huán)境出現(xiàn)了問(wèn)題,這樣錯(cuò)誤無(wú)法恢復(fù)或不可能捕獲,將導(dǎo)致應(yīng)用程序中斷。通常應(yīng)用程序無(wú)法處理這些錯(cuò)誤,因此應(yīng)用程序不應(yīng)該試圖使用catch塊來(lái)捕獲Error對(duì)象。在定義該方法時(shí),也無(wú)須在其throws子句聲明該方法能拋出Error及其任何子類
public class DivTest { public static void main(String[] args) { try { int a = Integer.parseInt(args[0]); int b = Integer.parseInt(args[1]); int c = a / b; System.out.println("您輸入的兩個(gè)數(shù)相除的結(jié)果是:" + c ); } catch (IndexOutOfBoundsException ie) { System.out.println("數(shù)組越界:運(yùn)行程序時(shí)輸入的參數(shù)個(gè)數(shù)不夠"); } catch (NumberFormatException ne) { System.out.println("數(shù)字格式異常:程序只能接受整數(shù)參數(shù)"); } catch (ArithmeticException ae) { System.out.println("算術(shù)異常"); } catch (Exception e) { System.out.println("未知異常"); } } }
如果運(yùn)行該程序時(shí)輸入的參數(shù)不夠,將會(huì)發(fā)生數(shù)組越界異常,Java運(yùn)行時(shí)將調(diào)用IndexOutOfBoundsException對(duì)應(yīng)的catch塊處理該異常
如果運(yùn)行該程序時(shí)輸入的參數(shù)不是數(shù)字,而是字母,將發(fā)生數(shù)字格式異常,Java運(yùn)行時(shí)將調(diào)用NumberFormatException對(duì)應(yīng)的catch塊處理該異常
如果運(yùn)行該程序時(shí)輸入的第二個(gè)參數(shù)是0,將發(fā)生除0異常,Java運(yùn)行時(shí)將調(diào)用ArithmeticException對(duì)應(yīng)的catch塊處理該異常
如果運(yùn)行該程序時(shí)出現(xiàn)其他異常,該異常對(duì)象總是Exception類或其子類的實(shí)例,Java運(yùn)行時(shí)將調(diào)用Exception對(duì)應(yīng)的catch塊處理該異常
public class NullTest { public static void main(String[] args) { Date d = null; try { System.out.println(d.after(new Date())); } catch (NullPointerException ne) { System.out.println("空指針異常"); } catch(Exception e) { System.out.println("未知異常"); } } }
如果運(yùn)行該程序時(shí)試圖調(diào)用一個(gè)null對(duì)象的實(shí)例方法或?qū)嵗兞繒r(shí),Java運(yùn)行時(shí)將調(diào)用NullPointerException對(duì)應(yīng)的catch塊處理該異常
實(shí)際上,進(jìn)行異常捕獲時(shí)不僅應(yīng)該把Exception類對(duì)應(yīng)的catch塊放在最后,而且所有父類異常的catch塊都應(yīng)該排在子類異常catch塊的后面(先處理小異常,再處理大異常),否則將出現(xiàn)編譯錯(cuò)誤
Java7提供的多異常捕獲一個(gè)catch塊可以捕獲多鐘類型的異常,使用一個(gè)catch塊捕獲多鐘類型的異常時(shí)需要注意如下兩個(gè)地方
捕獲多鐘類型的異常時(shí),多鐘異常之間用豎線(|)隔開(kāi)
捕獲多鐘類型的異常時(shí),異常變量有隱式的final修飾,因此程序不能對(duì)異常變量重新賦值
public class MultiExceptionTest { public static void main(String[] args) { try { int a = Integer.parseInt(args[0]); int b = Integer.parseInt(args[1]); int c = a / b; System.out.println("您輸入的兩個(gè)數(shù)相除的結(jié)果是:" + c ); } catch (IndexOutOfBoundsException | NumberFormatException |ArithmeticException ie) { System.out.println("程序發(fā)生了數(shù)組越界、數(shù)字格式異常、算術(shù)異常之一"); // 捕捉多異常時(shí),異常變量默認(rèn)有final修飾, // 所以下面代碼有錯(cuò): ie = new ArithmeticException("test"); // ① } catch (Exception e) { System.out.println("未知異常"); // 捕捉一個(gè)類型的異常時(shí),異常變量沒(méi)有final修飾 // 所以下面代碼完全正確。 e = new RuntimeException("test"); // ② } } }訪問(wèn)異常信息
如果程序需要在catch塊中訪問(wèn)異常對(duì)象的相關(guān)信息,則可以通過(guò)訪問(wèn)catch塊的后異常形參來(lái)獲得。當(dāng)Java運(yùn)行時(shí)決定調(diào)用某個(gè)catch塊來(lái)處理該異常對(duì)象時(shí),會(huì)將異常對(duì)象賦給catch塊后的異常參數(shù),程序即可通過(guò)該參數(shù)來(lái)獲得異常相關(guān)信息
getMessage():返回該異常的詳細(xì)描述字符串
printStackTrace():將該異常的跟蹤棧信息輸出到標(biāo)準(zhǔn)錯(cuò)誤輸出
printStackTrace(PrintStream s):將該異常的跟蹤棧信息輸出到標(biāo)準(zhǔn)錯(cuò)誤輸出
getStackTrace():返回該異常的跟蹤棧信息
public class AccessExceptionMsg { public static void main(String[] args) { try { FileInputStream fileInputStream =new FileInputStream("NBA.txt"); } catch (Exception ioe) { System.out.println(ioe.getMessage()); ioe.printStackTrace(); } } }使用finally回收資源
有些時(shí)候,程序在try塊里打開(kāi)了一些物理資源(例如數(shù)據(jù)庫(kù)連接、網(wǎng)絡(luò)連接和磁盤(pán)文件等),這些物理資源都必須顯式回收
Java垃圾回收機(jī)制不會(huì)回收任何物理資源,垃圾回收機(jī)制只能回收堆內(nèi)存中對(duì)象所占用的內(nèi)存
如果try塊的某條語(yǔ)句引起了異常,該語(yǔ)句后面的其他語(yǔ)句通常不會(huì)獲得執(zhí)行的機(jī)會(huì),這將導(dǎo)致位于該語(yǔ)句之后的資源回收語(yǔ)句得不到執(zhí)行。如果在catch塊里進(jìn)行資源回收,但catch塊完全有可能得不到執(zhí)行,這將導(dǎo)致不能及時(shí)回收這些物理資源
為了保證一定能回收try塊中打開(kāi)的物理資源,異常處理機(jī)制提供了finally塊。不管try塊中的代碼是否出現(xiàn)異常,也不管哪一個(gè)catch塊被執(zhí)行,甚至在try塊或catch塊中執(zhí)行了return語(yǔ)句,finally塊總會(huì)被執(zhí)行
try { // 業(yè)務(wù)實(shí)現(xiàn)代碼 ... } catch (SubException1 e) { // 異常處理塊1 ... } catch (SubException2 e) { // 異常處理塊2 ... } ... finally { // 資源回收塊 ... }
異常處理語(yǔ)法結(jié)構(gòu)中,只有try塊是必須的,也就是說(shuō),如果沒(méi)有try塊,則不能有后面的catch塊和finally塊;catch塊和finally塊都是可選的,但catch塊和finally塊至少出現(xiàn)其中之一,也可以同時(shí)出現(xiàn);可以有多個(gè)catch塊,捕獲父類異常的catch塊必須位于捕獲子類異常的后面;但不能只有try塊,既沒(méi)有catch塊,也沒(méi)有finally塊;多個(gè)catch塊必須位于try塊之后,finally塊必須位于所有的catch塊之后
public class FinallyTest { public static void main(String[] args) { FileInputStream fis = null; try { fis = new FileInputStream("a.txt"); } catch (IOException ioe) { System.out.println(ioe.getMessage()); // return語(yǔ)句強(qiáng)制方法返回 return ; // ① // 使用exit來(lái)退出虛擬機(jī) // System.exit(1); // ② } finally { // 關(guān)閉磁盤(pán)文件,回收資源 if (fis != null) { try { fis.close(); } catch (IOException ioe) { ioe.printStackTrace(); } } System.out.println("執(zhí)行finally塊里的資源回收!"); } } }
除非在try塊、catch塊中調(diào)用了退出虛擬機(jī)的方法,否則不管在try塊、catch塊中執(zhí)行怎樣的代碼,出現(xiàn)怎樣的情況,異常處理的finally塊總會(huì)被執(zhí)行
在通常情況下,不要在finally塊中使用如return或throw等導(dǎo)致方法終止的語(yǔ)句,一旦在finally塊中使用了return或throw語(yǔ)句,將會(huì)導(dǎo)致try塊、catch塊中的return、throw語(yǔ)句失效
當(dāng)Java程序執(zhí)行try塊,catch塊時(shí)遇到了return或throw語(yǔ)句,這兩個(gè)語(yǔ)句都會(huì)導(dǎo)致該方法立即結(jié)束,但是系統(tǒng)執(zhí)行這兩個(gè)語(yǔ)句并不會(huì)結(jié)束該方法,而是去尋找該異常處理流程中是否包含finally塊,如果沒(méi)有finally塊,程序立即執(zhí)行return或throw語(yǔ)句,方法終止;如果有finally塊,系統(tǒng)立即開(kāi)始執(zhí)行finally塊——只有當(dāng)finally塊執(zhí)行完后,系統(tǒng)才會(huì)再跳回來(lái)執(zhí)行try塊,catch塊里的return或throw語(yǔ)句。如果finally塊也使用了return或throw等導(dǎo)致方法終止的語(yǔ)句,finally塊已經(jīng)終止了方法,系統(tǒng)將不會(huì)再跳回去執(zhí)行try塊、catch塊里的任何代碼
盡量避免在finally里使用return或throw等導(dǎo)致方法終止的語(yǔ)句,否則可能出現(xiàn)一些很奇怪的情況
異常處理的嵌套在try塊、catch塊或finally塊中包含完整的異常處理流程的情形被稱為異常處理的嵌套
異常處理流程代碼可以放在任何可執(zhí)行性代碼的地方,因此完整的異常處理流程既可以放在try塊里,也可以放在catch塊里,還可以放在finally塊里
異常處理嵌套的深度沒(méi)有很明確的限制,但通常沒(méi)有必要使用超過(guò)兩層的嵌套異常處理,層次太深的嵌套異常處理沒(méi)有太大必要,而且導(dǎo)致程序可讀性降低
Java7的自動(dòng)關(guān)閉資源的try語(yǔ)句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ǔ)句在該語(yǔ)句結(jié)束時(shí)自動(dòng)關(guān)閉這些資源。為了保證try語(yǔ)句可以正常關(guān)閉資源,這些資源實(shí)現(xiàn)類必須實(shí)現(xiàn)Closeable或AutoCloseable接口,實(shí)現(xiàn)這些類就必須實(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ǔ)句
public class AutoCloseTest { public static void main(String[] args) throws IOException { try ( // 聲明、初始化兩個(gè)可關(guān)閉的資源 // try語(yǔ)句會(huì)自動(dòng)關(guān)閉這兩個(gè)資源。 BufferedReader br = new BufferedReader( new FileReader("AutoCloseTest.java")); PrintStream ps = new PrintStream(new FileOutputStream("a.txt"))) { // 使用兩個(gè)資源 System.out.println(br.readLine()); ps.println("莊生曉夢(mèng)迷蝴蝶"); } } }
上面程序圓括號(hào)里代碼分別聲明、初始化了兩個(gè)IO流,由于BufferedReader、PrintStream都實(shí)現(xiàn)了Closeable接口,而且它們放在try語(yǔ)句中聲明、初始化,所以try語(yǔ)句會(huì)自動(dòng)關(guān)閉它們。因此程序是安全的。自動(dòng)關(guān)閉資源的try語(yǔ)句相當(dāng)于包含了隱式的finally塊(這個(gè)finally塊用于關(guān)閉資源),因此這個(gè)try語(yǔ)句可以既沒(méi)有catch塊,也沒(méi)有finally塊
自動(dòng)關(guān)閉資源的try語(yǔ)句后也可以帶多個(gè)catch塊和一個(gè)finally塊
Checked異常和Runtime異常體系Java的異常被分為兩大類:Checked異常和Runtime異常(運(yùn)行時(shí)異常)。所有RuntimeException類及其子類實(shí)例被稱為Runtime異常;不是RuntimeException類及其子類的異常實(shí)例則稱為Checked異常
只有Java語(yǔ)言提供了Checked異常,其他語(yǔ)言都沒(méi)有提供Checked異常。Java認(rèn)為Checked異常都是可以被處理(修復(fù))的異常,所以Java程序必須顯式處理Checked異常
對(duì)于Checked異常的處理方式有兩種
當(dāng)前方法明確知道如何處理該異常,程序應(yīng)該使用try...catch塊來(lái)捕獲該異常,然后在對(duì)應(yīng)的catch塊中修補(bǔ)該異常
當(dāng)前方法不知道如何處理這種異常,應(yīng)該在定義該方法時(shí)聲明拋出該異常
Runtime異常則更加靈活,Runtime異常無(wú)須顯式聲明拋出,如果程序需要捕捉Runtime異常,也可以使用try...catch塊來(lái)實(shí)現(xiàn)
使用throws聲明拋出異常使用throws聲明拋出異常的思路是,當(dāng)前方法不知道如何處理這種類型的異常,該異常應(yīng)該由上一級(jí)調(diào)用者處理;如果main方法也不知道如何處理這種類型的異常,也可以使用throws聲明拋出異常,該異常交給JVM處理。JVM對(duì)異常處理的方法是打印異常的跟蹤棧信息,并中止程序運(yùn)行,這就是前面程序在遇到異常后自動(dòng)結(jié)束的原因
throws聲明拋出只能在方法簽名中進(jìn)行使用,throws可以聲明拋出多個(gè)異常類,多個(gè)異常類之間以逗號(hào)隔開(kāi)
throws Exception1, Exception2...
下面使用了throws來(lái)聲明拋出IOException異常,一旦使用throws語(yǔ)句聲明拋出該異常,程序就無(wú)須使用try...catch塊來(lái)捕獲該異常了。程序聲明不處理IOException異常,將該異常交給JVM處理,所以程序一旦遇到該異常,JVM就會(huì)打印該異常的跟蹤棧信息,并結(jié)束程序
public class ThrowsTest { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("a.txt"); } }
如果某段代碼中調(diào)用了一個(gè)帶throws聲明的方法,該方法聲明拋出了Checked異常,則表明該方法希望它的調(diào)用者來(lái)處理該異常。也就是說(shuō),調(diào)用該方法時(shí)要么放在try塊中顯式捕獲該異常,要么放在另一個(gè)帶throws聲明拋出的方法中
public class ThrowsTest2 { public static void main(String[] args) throws Exception { // 因?yàn)閠est()方法聲明拋出IOException異常, // 所以調(diào)用該方法的代碼要么處于try...catch塊中, // 要么處于另一個(gè)帶throws聲明拋出的方法中。 test(); } public static void test()throws IOException { // 因?yàn)镕ileInputStream的構(gòu)造器聲明拋出IOException異常, // 所以調(diào)用FileInputStream的代碼要么處于try...catch塊中, // 要么處于另一個(gè)帶throws聲明拋出的方法中。 FileInputStream fis = new FileInputStream("a.txt"); } }
子類方法聲明拋出的異常類型應(yīng)該是父類方法聲明拋出的異常類型的子類或相同,子類方法聲明拋出的異常不允許比父類方法聲明拋出的異常多
public class OverrideThrows { public void test()throws IOException { FileInputStream fis = new FileInputStream("a.txt"); } } class Sub extends OverrideThrows { // 子類方法聲明拋出了比父類方法更大的異常 // 所以下面方法出錯(cuò) public void test()throws Exception { } }
使用Checked異常至少存在如下兩大不便之處
對(duì)于程序中的Checked異常,java要求必須顯式捕獲并處理該異常,或者顯式聲明拋出該異常。這樣就增加了編程復(fù)雜度
如果在方法中顯式聲明拋出Checked異常,將會(huì)導(dǎo)致方法簽名與異常耦合,如果該方法是重寫(xiě)父類的方法,則該方法拋出的異常還會(huì)受到被重寫(xiě)方法所拋出異常的限制
在大部分時(shí)候推薦使用Runtime異常,而不使用Checked異常。尤其當(dāng)程序需要自行拋出異常時(shí),使用Runtime異常更加簡(jiǎn)潔。當(dāng)使用Runtime異常時(shí),程序無(wú)須在方法中聲明拋出Checked異常,一旦發(fā)生了自定義錯(cuò)誤,程序只管拋出Runtime異常即可
使用throw拋出異常當(dāng)程序出現(xiàn)錯(cuò)誤時(shí),系統(tǒng)會(huì)自動(dòng)拋出異常;Java允許程序自行拋出異常,自行拋出異常使用throw語(yǔ)句來(lái)完成。
拋出異常throw語(yǔ)句可以多帶帶使用,throw語(yǔ)句拋出的不是異常類,而是一個(gè)異常實(shí)例,而且每次只能拋出一個(gè)異常實(shí)例。throw語(yǔ)句的語(yǔ)法格式如下:
throw ExceptionInsantance;
當(dāng)Java運(yùn)行時(shí)接收到開(kāi)發(fā)者自行拋出的異常時(shí),同樣會(huì)中止當(dāng)前的執(zhí)行流,跳到該異常對(duì)應(yīng)的catch塊,由該catch塊來(lái)處理該異常。也就是說(shuō),不管是系統(tǒng)自動(dòng)拋出的異常,還是程序員手動(dòng)拋出的異常,Java運(yùn)行時(shí)環(huán)境對(duì)異常的處理沒(méi)有任何差別
如果throw語(yǔ)句拋出的異常是Checked異常,則該throw語(yǔ)句要么處于try塊里,顯式捕獲該異常,要么放在一個(gè)帶throws聲明拋出的方法中,即把該異常交給該方法的調(diào)用者處理;如果throw語(yǔ)句拋出的異常是Runtime異常,則該語(yǔ)句無(wú)須放在try塊里,也無(wú)須放在帶throws聲明拋出的方法中;程序既可以顯式使用try…catch來(lái)捕獲并處理該異常,也可以完全不理會(huì)該異常,把該異常交給方法調(diào)用者處理
public class ThrowTest { public static void main(String[] args) { try { // 調(diào)用聲明拋出Checked異常的方法,要么顯式捕獲該異常 // 要么在main方法中再次聲明拋出 throwChecked(-3); } catch (Exception e) { System.out.println(e.getMessage()); } // 調(diào)用聲明拋出Runtime異常的方法既可以顯式捕獲該異常, // 也可不理會(huì)該異常 throwRuntime(3); } public static void throwChecked(int a)throws Exception { if (a > 0) { // 自行拋出Exception異常 // 該代碼必須處于try塊里,或處于帶throws聲明的方法中 throw new Exception("a的值大于0,不符合要求"); } } public static void throwRuntime(int a) { if (a > 0) { // 自行拋出RuntimeException異常,既可以顯式捕獲該異常 // 也可完全不理會(huì)該異常,把該異常交給該方法調(diào)用者處理 throw new RuntimeException("a的值大于0,不符合要求"); } } }
自行拋出Runtime異常比自行拋出Checked異常的靈活性更好。拋出Checked異常則可以讓編譯器提醒程序員必須處理該異常
自定義異常類在通常情況下,程序很少會(huì)自行拋出系統(tǒng)異常,因?yàn)楫惓5念惷ǔR舶嗽摦惓5挠杏眯畔?。所以在選擇拋出異常時(shí),應(yīng)該選擇合適的異常類,從而可以明確地描述該異常情況。在這種情形下,應(yīng)用程序常常需要拋出自定義異常
自定義異常都應(yīng)該繼承Exception基類,如果希望自定義Runtime異常,則應(yīng)該繼承RuntimeException基類。定義異常類時(shí)通常需要提供兩個(gè)構(gòu)造器:一個(gè)是無(wú)參數(shù)的構(gòu)造器;另一個(gè)是帶一個(gè)字符串參數(shù)的構(gòu)造器,這個(gè)字符串將作為該異常對(duì)象的描述信息(也就是異常對(duì)象的getMessage()方法的返回值)
public class AuctionException extends Exception { // 無(wú)參構(gòu)造器 public AuctionException(){} // 帶一個(gè)字符串參數(shù)的構(gòu)造器 public AuctionException(String msg) { super(msg); } }
在大部分情況下,創(chuàng)建自定義異常都可采用與AuctionException.java相似的代碼完成,只需改變AuctionException異常的類名即可,讓該異常類的類名可以準(zhǔn)確描述該異常
catch和throw同時(shí)使用兩種異常處理方式
在出現(xiàn)異常的方法內(nèi)捕獲并處理異常,該方法的調(diào)用者將不能再次捕獲該異常
該方法簽名中聲明拋出該異常,將該異常完全交給方法調(diào)用者處理
當(dāng)一個(gè)異常出現(xiàn)時(shí),單靠某個(gè)方法無(wú)法完全處理該異常,必須由幾個(gè)方法協(xié)作才可以完全處理該異常。也就是說(shuō),在異常出現(xiàn)的當(dāng)前方法中,程序只對(duì)異常進(jìn)行部分處理,還有些處理需要在該方法的調(diào)用者中才能完成,所以應(yīng)該再次拋出異常,讓該方法的調(diào)用者也能捕獲到異常
為了實(shí)現(xiàn)這種通過(guò)多個(gè)方法協(xié)作處理同一異常的情形,可以在catch塊中結(jié)合throw語(yǔ)句來(lái)完成
public class AuctionTest { private double initPrice = 30.0; // 因?yàn)樵摲椒ㄖ酗@式拋出了AuctionException異常, // 所以此處需要聲明拋出AuctionException異常 public void bid(String bidPrice) throws AuctionException { double d = 0.0; try { d = Double.parseDouble(bidPrice); } catch (Exception e) { // 此處完成本方法中可以對(duì)異常執(zhí)行的修復(fù)處理, // 此處僅僅是在控制臺(tái)打印異常跟蹤棧信息。 e.printStackTrace(); // 再次拋出自定義異常 throw new AuctionException("競(jìng)拍價(jià)必須是數(shù)值," + "不能包含其他字符!"); } if (initPrice > d) { throw new AuctionException("競(jìng)拍價(jià)比起拍價(jià)低," + "不允許競(jìng)拍!"); } initPrice = d; } public static void main(String[] args) { AuctionTest at = new AuctionTest(); try { at.bid("df"); } catch (AuctionException ae) { // 再次捕捉到bid方法中的異常。并對(duì)該異常進(jìn)行處理 System.err.println(ae.getMessage()); } } }
這種catch和throw結(jié)合使用的情況在大型企業(yè)級(jí)應(yīng)用中非常常用。企業(yè)級(jí)應(yīng)用對(duì)異常的處理通常分成兩個(gè)部
應(yīng)用后臺(tái)需要通過(guò)日志來(lái)記錄異常發(fā)生的詳細(xì)情況
應(yīng)用還需要根據(jù)異常向應(yīng)用傳達(dá)某種提示
在這種情形下,所有異常都需要兩個(gè)方法共同完成,也就必須將catch和throw結(jié)合使用
異常鏈對(duì)于真實(shí)的企業(yè)級(jí)應(yīng)用而言,常常有嚴(yán)格的分層關(guān)系,層與層之間有非常清晰的劃分,上層功能的實(shí)現(xiàn)嚴(yán)格依賴于下層的API,也不會(huì)跨層訪問(wèn)。下圖顯式了這種具有分層結(jié)構(gòu)應(yīng)用的大致示意圖
程序先捕獲原始的異常,然后拋出一個(gè)新的業(yè)務(wù)異常,新的業(yè)務(wù)異常中包含了對(duì)用戶的提示信息,這種處理方式被稱為異常轉(zhuǎn)譯
public calSal throws SalException { try { //實(shí)現(xiàn)結(jié)算工資的業(yè)務(wù)邏輯 .... } catch(SQLException sqle) { //把原始異常記錄下來(lái),留個(gè)管理員 ... //下面異常中的message就是向用戶的提示 throw new SalException("訪問(wèn)底層數(shù)據(jù)庫(kù)出現(xiàn)異常"); } catch(Exception e) { //把原始異常記錄下來(lái),留個(gè)管理員 .... //下面異常中的message就是向用戶的提示 throw new SalException("系統(tǒng)出現(xiàn)未知異常"); } }
這種把捕獲一個(gè)異常然后接著拋出另一個(gè)異常,并把原始異常信息保存下來(lái)是一種典型的鏈?zhǔn)教幚恚氊?zé)鏈模式),也稱為“異常鏈”
所有的Throwable子類在構(gòu)造器中都可以接受一個(gè)cause對(duì)象作為參數(shù)。這個(gè)cause就用來(lái)表示原始異常,這樣可以把原始異常傳遞給新的異常,使得即使在當(dāng)前位置創(chuàng)建并拋出了新的異常,你也能通過(guò)這個(gè)異常鏈追蹤到異常最初發(fā)生的位置。如果我們希望上面SalException可以追蹤到最原始的異常信息。則可以將該方法改寫(xiě)如下
public calSal throws SalException { try { //實(shí)現(xiàn)結(jié)算工資的業(yè)務(wù)邏輯 .... } catch(SQLException sqle) { //把原始異常記錄下來(lái),留個(gè)管理員 ... //下面異常中的message就是向用戶的提示 throw new SalException(sqle); } catch(Exception e) { //把原始異常記錄下來(lái),留個(gè)管理員 .... //下面異常中的message就是向用戶的提示 throw new SalException(e); } }
上面程序中創(chuàng)建SalException對(duì)象時(shí),傳入了一個(gè)Exception對(duì)象,而不是傳入了一個(gè)String對(duì)象,這就需要SalException類有相應(yīng)的構(gòu)造器。從JDK1.4以后,Throwable基類有一個(gè)可以接收Exception參數(shù)的方法,所以可以采用如下代碼來(lái)定義SalException類
public class SalException extends Exception { public SalException(){} public SalException(String msg) { super(msg); } public SalException(Throwable t) { super(t); } }Java的異常跟蹤棧
異常對(duì)象的printStackTrace()方法用于打印異常的跟蹤棧信息,根據(jù)printStackTrace()方法的輸出結(jié)果,開(kāi)發(fā)者可以找到異常的源頭,并跟蹤到異常一路觸發(fā)的過(guò)程
class SelfException extends RuntimeException { SelfException(){} SelfException(String msg) { super(msg); } } public class PrintStackTraceTest { public static void main(String[] args) { firstMethod(); } public static void firstMethod() { secondMethod(); } public static void secondMethod() { thirdMethod(); } public static void thirdMethod() { throw new SelfException("自定義異常信息"); } }
從結(jié)果可知,異常從thirdMethod方法開(kāi)始觸發(fā),傳到secondMethod方法,再傳到firstMethod方法,最后傳到main方法,在main方法終止,這個(gè)過(guò)程就是Java的異常跟蹤棧
Exception in thread "main" SelfException: 自定義異常信息 at PrintStackTraceTest.thirdMethod(PrintStackTraceTest.java:25) at PrintStackTraceTest.secondMethod(PrintStackTraceTest.java:21) at PrintStackTraceTest.firstMethod(PrintStackTraceTest.java:17) at PrintStackTraceTest.main(PrintStackTraceTest.java:13)
只要異常沒(méi)有被完全捕獲(包括異常沒(méi)有捕獲,或異常被處理后重新拋出了新異常),異常從發(fā)生異常的方法逐漸向外傳播,首先傳給該方法的調(diào)用者,該方法調(diào)用者再次傳給其調(diào)用者...直至最后傳到main方法,如果main方法依然沒(méi)有處理該異常,JVM會(huì)中止該程序,并打印異常的跟蹤棧信息
跟蹤棧記錄程序中所有的異常發(fā)生點(diǎn),各行顯式被調(diào)用方法中執(zhí)行的停止位置,并標(biāo)明類、類中的方法名、與故障點(diǎn)對(duì)應(yīng)的文件的行。一行行地往下看,跟蹤??偸亲顑?nèi)部的被調(diào)用方法逐漸上傳,直到最外部業(yè)務(wù)操作的起點(diǎn),通常就是程序的入口main方法或Thread類的run方法(多線程)
public class ThreadExceptionTest implements Runnable { public void run() { firstMethod(); } public void firstMethod() { secondMethod(); } public void secondMethod() { int a = 5; int b = 0; int c = a / b; } public static void main(String[] args) { new Thread(new ThreadExceptionTest()).start(); } }
程序在Thread的run方法中出現(xiàn)了ArithmeticException異常,這個(gè)異常的源頭是ThreadExceptionTest的secondMethod方法。這個(gè)異常傳播到Thread類的run方法就會(huì)結(jié)束(如果該異常沒(méi)有得到處理,將會(huì)導(dǎo)致該線程中止運(yùn)行)
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero at ThreadExceptionTest.secondMethod(ThreadExceptionTest.java:15) at ThreadExceptionTest.firstMethod(ThreadExceptionTest.java:9) at ThreadExceptionTest.run(ThreadExceptionTest.java:5) at java.lang.Thread.run(Unknown Source)異常處理規(guī)則
使程序代碼混亂最小化
捕獲并保留診斷信息
通知合適的人員
采用合適的方式結(jié)束異?;顒?dòng)
不要過(guò)度使用異常過(guò)度使用異常主要有兩個(gè)方面:
把異常和普通錯(cuò)誤混淆在一起,不再編寫(xiě)任何錯(cuò)誤處理代碼,而是以簡(jiǎn)單地拋出異常來(lái)代替所有的錯(cuò)誤處理
使用異常處理來(lái)代替流程控制
異常處理機(jī)制的初衷是將不可預(yù)期的處理代碼和正常的業(yè)務(wù)邏輯處理代碼分離,因此絕不要使用異常處理來(lái)代替正常的業(yè)務(wù)邏輯判斷。另外,異常機(jī)制的效率比正常的流程控制效率差,所以不要使用異常處理來(lái)代替正常的程序流程控制
異常只應(yīng)該用于處理非正常的情況,不要使用異常處理來(lái)代替正常的流程控制。對(duì)于一些完全可預(yù)知,而且處理方式清楚的錯(cuò)誤,程序應(yīng)該提供相應(yīng)的錯(cuò)誤處理代碼,而不是將其籠統(tǒng)地稱為異常
不要使用過(guò)于龐大的try塊正確的做法是,把大塊的try塊分割成多個(gè)可能出現(xiàn)異常的程序段落,并把它們放在多帶帶的try塊中,從而分別捕獲并處理異常
避免使用Catch All語(yǔ)句所謂Catch All語(yǔ)句指的是一種異常捕獲模塊,它可以處理程序發(fā)生的所有可能異常
try { // code here with checked exceptions } catch (Throwable t) { //exception handler t.printStackTrace(); }
這種處理方式有如下兩點(diǎn)不足之處:
所有異常都采用相同的處理方式,這將導(dǎo)致無(wú)法對(duì)不同異常分情況處理,如果要分情況處理,則需要在catch塊中使用分支語(yǔ)句進(jìn)行控制,這是得不償失的做法。
這種捕獲方式可能將程序中的錯(cuò)誤、Runtime異常等可能導(dǎo)致程序終止的情況全部捕獲到,從而 “壓制”了異常。如果出現(xiàn)了一些“關(guān)鍵”異常,那個(gè)異常也會(huì)被“靜悄悄”地忽略
不要忽略捕獲到的異常通常建議對(duì)異常進(jìn)行適當(dāng)措施:
處理異常。對(duì)異常采用合適的修補(bǔ),然后繞過(guò)異常發(fā)生的地方繼續(xù)執(zhí)行;或者用別的數(shù)據(jù)進(jìn)行計(jì)算,以代替期望的方法返回值;或者提示用戶重新操作......總之,對(duì)于Checked異常,程序應(yīng)該盡量采用修復(fù)
重新拋出新異常。把當(dāng)前運(yùn)行環(huán)境下能做的事情盡量作完,然后進(jìn)行異常轉(zhuǎn)譯,把異常包裝成當(dāng)前層的異常,重新拋出給上層調(diào)用者
在合適的層處理異常。如果當(dāng)前層不清楚如何處理異常,就不要在當(dāng)前層使用catch語(yǔ)句來(lái)捕獲該異常,直接使用throws聲明拋出該異常,讓上層調(diào)用者來(lái)負(fù)責(zé)處理該異常
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/66440.html
摘要:不受檢查異常為編譯器不要求強(qiáng)制處理的異常,檢查異常則是編譯器要求必須處置的異常。潛在的異常處理器是異常發(fā)生時(shí)依次存留在調(diào)用棧中的方法的集合。當(dāng)運(yùn)行時(shí)系統(tǒng)遍歷調(diào)用棧而未找到合適的異常處理器,則運(yùn)行時(shí)系統(tǒng)終止。異常處理涉及到五個(gè)關(guān)鍵字,分別是。 概念 程序運(yùn)行時(shí),發(fā)生的不被期望的事件,它阻止了程序按照程序員的預(yù)期正常執(zhí)行,這就是異常。 異常是程序中的一些錯(cuò)誤,但并不是所有的錯(cuò)誤都是異常,并...
摘要:異常也就是指程序運(yùn)行時(shí)發(fā)生錯(cuò)誤,而異常處理就是對(duì)這些錯(cuò)誤進(jìn)行處理和控制。有兩個(gè)重要的子類異常和錯(cuò)誤,二者都是異常處理的重要子類,各自都包含大量子類。需要注意的是,一旦某個(gè)捕獲到匹配的異常類型,將進(jìn)入異常處理代碼。 1,異?,F(xiàn)象 程序錯(cuò)誤分為三種:1,編譯錯(cuò)誤;2,運(yùn)行時(shí)錯(cuò)誤;3,邏輯錯(cuò)誤。 編譯錯(cuò)誤是因?yàn)槌绦驔](méi)有遵循語(yǔ)法規(guī)則,編譯程序能夠自己發(fā)現(xiàn)并且提示我們錯(cuò)誤的原因和位置,這...
摘要:可以被異常處理機(jī)制使用,是異常處理的核心。非檢測(cè)異常,在編譯時(shí),不會(huì)提示和發(fā)現(xiàn)異常的存在,不強(qiáng)制要求程序員處理這樣的異常??傮w來(lái)說(shuō),語(yǔ)言的異常處理流程,從程序中獲取異常信息。處理運(yùn)行時(shí)異常,采用邏輯合理規(guī)避同時(shí)輔助處理。 目錄 什么是Java異常? 當(dāng)一個(gè)Exception在程序中發(fā)生的時(shí)候,JVM是怎么做的呢? 當(dāng)我們編寫(xiě)程序的時(shí)候如何對(duì)待可能出現(xiàn)的異常呢? 正文 1. 什么是J...
摘要:根據(jù)異常對(duì)象判斷是否存在異常處理。否則,范圍小的異常會(huì)因異常處理完成而無(wú)法處理。異常處理中使用作為異常的統(tǒng)一出口。 參考《第一行代碼java》《java程序設(shè)計(jì)教程》java中程序的錯(cuò)誤有語(yǔ)法錯(cuò)誤、語(yǔ)義錯(cuò)誤。如果是語(yǔ)法性錯(cuò)誤,在編譯時(shí)就可以檢查出來(lái)并解決。語(yǔ)義錯(cuò)誤是在程序運(yùn)行時(shí)出現(xiàn)的,在編譯時(shí)沒(méi)有錯(cuò)誤,但在運(yùn)行時(shí)可能會(huì)出現(xiàn)錯(cuò)誤導(dǎo)致程序退出,這些錯(cuò)誤稱為異常。在沒(méi)有異常處理的情況下,也即...
摘要:為可恢復(fù)的錯(cuò)誤使用檢查型異常,為編程錯(cuò)誤使用非檢查型錯(cuò)誤。檢查型異常保證你對(duì)錯(cuò)誤條件提供異常處理代碼,這是一種從語(yǔ)言到強(qiáng)制你編寫(xiě)健壯的代碼的一種方式,但同時(shí)會(huì)引入大量雜亂的代碼并導(dǎo)致其不可讀。在編程中選擇檢查型異常還是運(yùn)行時(shí)異常。 異常處理是Java 開(kāi)發(fā)中的一個(gè)重要部分。它是關(guān)乎每個(gè)應(yīng)用的一個(gè)非功能性需求,是為了處理任何錯(cuò)誤狀況,比如資源不可訪問(wèn),非法輸入,空輸入等等。Java提供了...
閱讀 2242·2021-11-22 15:29
閱讀 4118·2021-11-04 16:13
閱讀 1002·2019-08-29 16:58
閱讀 349·2019-08-29 16:08
閱讀 1469·2019-08-23 17:56
閱讀 2396·2019-08-23 17:06
閱讀 3174·2019-08-23 16:55
閱讀 2070·2019-08-23 16:22