日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java 异常处理

發(fā)布時(shí)間:2024/4/14 java 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 异常处理 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Java的異常機(jī)制主要依賴于try、catch、finally、throw和throws五個(gè)關(guān)鍵字,

  • try關(guān)鍵字后緊跟一個(gè)花括號(hào)括起來的代碼塊(花括號(hào)不可省略),簡稱try塊,它里面放置可能引發(fā)異常的代碼

  • catch后對(duì)應(yīng)異常類型和一個(gè)代碼塊,用于表明該catch塊用于處理這種類型的代碼塊

  • 多個(gè)catch塊后還可以跟一個(gè)finally塊,finally塊用于回收在try塊里打開的物理資源,異常機(jī)制會(huì)保證finally塊總被執(zhí)行

  • throws關(guān)鍵字主要在方法簽名中使用,拋出一個(gè)具體的異常對(duì)象

  • throw用于拋出一個(gè)實(shí)際的異常,throw可以單獨(dú)作為語句使用,拋出一個(gè)具體的異常對(duì)象

我們希望所有的錯(cuò)誤都可以在編譯階段被發(fā)現(xiàn),就是在試圖運(yùn)行程序之前排除所有錯(cuò)誤,但這是不現(xiàn)實(shí)的,余下的問題必須在運(yùn)行期間得到解決。JAVA將異常分為兩種,Checked異常和Runtime異常,JAVA認(rèn)為Checked異常都是可以在編譯階段被處理的異常,所以他強(qiáng)制程序處理所有多的Checked異常;而Runtime異常則無需處理

異常機(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ì)象來通知程序,從而實(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ī)制的語法結(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è)過程被稱為拋出(throw)異常。當(dāng)Java運(yùn)行時(shí)環(huán)境收到異常對(duì)象時(shí),會(huì)尋找能處理該異常的catch塊,如果找到合適的catch塊,則把該異常對(duì)象交給catch塊處理,這個(gè)過程被稱為捕獲(catch)異常;如果Java運(yùn)行時(shí)找不到捕獲異常的catch塊,則運(yùn)行時(shí)環(huán)境終止,Java程序也將退出

String inputStr = null; // br.readLine():每當(dāng)在鍵盤上輸入一行內(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塊來處理該異常;否則再次判斷該異常對(duì)象和下一個(gè)catch塊里的異常類進(jìn)行比較

Java異常捕獲流程示意圖

當(dāng)程序進(jìn)入負(fù)責(zé)異常處理的catch塊時(shí),系統(tǒng)生成的異常對(duì)象ex將會(huì)傳給catch塊后的異常形參,從而允許catch塊通過該對(duì)象來獲得異常的詳細(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ì)象所屬的異常類來決定使用哪個(gè)catch塊來處理該異常

通過在try塊后提供多個(gè)catch塊可以無須在異常處理塊中使用if、switch判斷異常類型,但依然可以針對(duì)不同異常類型提供相應(yīng)的處理邏輯,從而提供更細(xì)致,更有調(diào)理的異常處理邏輯

從上圖可以看出,通常情況下,如果try塊被執(zhí)行一次,則try塊后只有一個(gè)catch塊會(huì)被執(zhí)行,絕不可能有多個(gè)catch塊被執(zhí)行,除非在循環(huán)中使用了continue開始下一次循環(huán),下一次循環(huán)又重新運(yùn)行了try塊,這才可能導(dǎo)致多個(gè)catch塊被執(zhí)行

try塊與if語句不一樣,try塊后的花括號(hào)({...})不可以省略,即使try塊里只有一行代碼,也不可以省略這個(gè)花括號(hào)。與之類似的,catch塊后的花括號(hào)({...})也不可以省略。還有一點(diǎn)需要指出:try塊里聲明的變量是代碼塊內(nèi)局部變量,它只在try塊內(nèi)有效,catch塊中不能訪問該變量。

Java常見的異常類之間的繼承關(guān)系圖

Java把所有非正常情況分成兩種:異常(Exception)和錯(cuò)誤(Error),它們都是繼承Throwable父類

Error錯(cuò)誤,一般是指與虛擬機(jī)(JVM)相關(guān)的問題,如系統(tǒng)崩潰、虛擬機(jī)出錯(cuò)誤、動(dòng)態(tài)鏈接失敗等,這種錯(cuò)誤是java程序的根本運(yùn)行環(huán)境出現(xiàn)了問題,這樣錯(cuò)誤無法恢復(fù)或不可能捕獲,將導(dǎo)致應(yīng)用程序中斷。通常應(yīng)用程序無法處理這些錯(cuò)誤,因此應(yīng)用程序不應(yīng)該試圖使用catch塊來捕獲Error對(duì)象。在定義該方法時(shí),也無須在其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í),多鐘異常之間用豎線(|)隔開

  • 捕獲多鐘類型的異常時(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í),異常變量沒有final修飾// 所以下面代碼完全正確。e = new RuntimeException("test"); // ②}} }

訪問異常信息

如果程序需要在catch塊中訪問異常對(duì)象的相關(guān)信息,則可以通過訪問catch塊的后異常形參來獲得。當(dāng)Java運(yùn)行時(shí)決定調(diào)用某個(gè)catch塊來處理該異常對(duì)象時(shí),會(huì)將異常對(duì)象賦給catch塊后的異常參數(shù),程序即可通過該參數(shù)來獲得異常相關(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塊里打開了一些物理資源(例如數(shù)據(jù)庫連接、網(wǎng)絡(luò)連接和磁盤文件等),這些物理資源都必須顯式回收

Java垃圾回收機(jī)制不會(huì)回收任何物理資源,垃圾回收機(jī)制只能回收堆內(nèi)存中對(duì)象所占用的內(nèi)存

如果try塊的某條語句引起了異常,該語句后面的其他語句通常不會(huì)獲得執(zhí)行的機(jī)會(huì),這將導(dǎo)致位于該語句之后的資源回收語句得不到執(zhí)行。如果在catch塊里進(jìn)行資源回收,但catch塊完全有可能得不到執(zhí)行,這將導(dǎo)致不能及時(shí)回收這些物理資源

為了保證一定能回收try塊中打開的物理資源,異常處理機(jī)制提供了finally塊。不管try塊中的代碼是否出現(xiàn)異常,也不管哪一個(gè)catch塊被執(zhí)行,甚至在try塊或catch塊中執(zhí)行了return語句,finally塊總會(huì)被執(zhí)行

try {// 業(yè)務(wù)實(shí)現(xiàn)代碼... } catch (SubException1 e) {// 異常處理塊1... } catch (SubException2 e) {// 異常處理塊2... } ... finally {// 資源回收塊... }

異常處理語法結(jié)構(gòu)中,只有try塊是必須的,也就是說,如果沒有try塊,則不能有后面的catch塊和finally塊;catch塊和finally塊都是可選的,但catch塊和finally塊至少出現(xiàn)其中之一,也可以同時(shí)出現(xiàn);可以有多個(gè)catch塊,捕獲父類異常的catch塊必須位于捕獲子類異常的后面;但不能只有try塊,既沒有catch塊,也沒有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語句強(qiáng)制方法返回return ; // ①// 使用exit來退出虛擬機(jī)// System.exit(1); // ②}finally{// 關(guā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)致方法終止的語句,一旦在finally塊中使用了return或throw語句,將會(huì)導(dǎo)致try塊、catch塊中的return、throw語句失效

當(dāng)Java程序執(zhí)行try塊,catch塊時(shí)遇到了return或throw語句,這兩個(gè)語句都會(huì)導(dǎo)致該方法立即結(jié)束,但是系統(tǒng)執(zhí)行這兩個(gè)語句并不會(huì)結(jié)束該方法,而是去尋找該異常處理流程中是否包含finally塊,如果沒有finally塊,程序立即執(zhí)行return或throw語句,方法終止;如果有finally塊,系統(tǒng)立即開始執(zhí)行finally塊——只有當(dāng)finally塊執(zhí)行完后,系統(tǒng)才會(huì)再跳回來執(zhí)行try塊,catch塊里的return或throw語句。如果finally塊也使用了return或throw等導(dǎo)致方法終止的語句,finally塊已經(jīng)終止了方法,系統(tǒng)將不會(huì)再跳回去執(zhí)行try塊、catch塊里的任何代碼

盡量避免在finally里使用return或throw等導(dǎo)致方法終止的語句,否則可能出現(xiàn)一些很奇怪的情況

異常處理的嵌套

在try塊、catch塊或finally塊中包含完整的異常處理流程的情形被稱為異常處理的嵌套

異常處理流程代碼可以放在任何可執(zhí)行性代碼的地方,因此完整的異常處理流程既可以放在try塊里,也可以放在catch塊里,還可以放在finally塊里

異常處理嵌套的深度沒有很明確的限制,但通常沒有必要使用超過兩層的嵌套異常處理,層次太深的嵌套異常處理沒有太大必要,而且導(dǎo)致程序可讀性降低

Java7的自動(dòng)關(guān)閉資源的try語句

Java7增強(qiáng)了try語句的功能,它允許在try關(guān)鍵字后緊跟一對(duì)圓括號(hào),圓括號(hào)可以聲明、初始化一個(gè)或多個(gè)資源,此處的資源指的是那些必須在程序結(jié)束時(shí)顯式關(guān)閉的資源(比如數(shù)據(jù)庫連接、網(wǎng)絡(luò)連接等),try語句在該語句結(jié)束時(shí)自動(dòng)關(guān)閉這些資源。為了保證try語句可以正常關(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語句

public class AutoCloseTest {public static void main(String[] args)throws IOException{try (// 聲明、初始化兩個(gè)可關(guān)閉的資源// try語句會(huì)自動(dòng)關(guān)閉這兩個(gè)資源。BufferedReader br = new BufferedReader(new FileReader("AutoCloseTest.java"));PrintStream ps = new PrintStream(newFileOutputStream("a.txt"))){// 使用兩個(gè)資源System.out.println(br.readLine());ps.println("莊生曉夢(mèng)迷蝴蝶");}} }

上面程序圓括號(hào)里代碼分別聲明、初始化了兩個(gè)IO流,由于BufferedReader、PrintStream都實(shí)現(xiàn)了Closeable接口,而且它們放在try語句中聲明、初始化,所以try語句會(huì)自動(dòng)關(guān)閉它們。因此程序是安全的。自動(dòng)關(guān)閉資源的try語句相當(dāng)于包含了隱式的finally塊(這個(gè)finally塊用于關(guān)閉資源),因此這個(gè)try語句可以既沒有catch塊,也沒有finally塊

自動(dòng)關(guān)閉資源的try語句后也可以帶多個(gè)catch塊和一個(gè)finally塊

Checked異常和Runtime異常體系

Java的異常被分為兩大類:Checked異常和Runtime異常(運(yùn)行時(shí)異常)。所有RuntimeException類及其子類實(shí)例被稱為Runtime異常;不是RuntimeException類及其子類的異常實(shí)例則稱為Checked異常

只有Java語言提供了Checked異常,其他語言都沒有提供Checked異常。Java認(rèn)為Checked異常都是可以被處理(修復(fù))的異常,所以Java程序必須顯式處理Checked異常

對(duì)于Checked異常的處理方式有兩種

  • 當(dāng)前方法明確知道如何處理該異常,程序應(yīng)該使用try...catch塊來捕獲該異常,然后在對(duì)應(yīng)的catch塊中修補(bǔ)該異常

  • 當(dāng)前方法不知道如何處理這種異常,應(yīng)該在定義該方法時(shí)聲明拋出該異常

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聲明拋出只能在方法簽名中進(jìn)行使用,throws可以聲明拋出多個(gè)異常類,多個(gè)異常類之間以逗號(hào)隔開

throws Exception1, Exception2...

下面使用了throws來聲明拋出IOException異常,一旦使用throws語句聲明拋出該異常,程序就無須使用try...catch塊來捕獲該異常了。程序聲明不處理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)用者來處理該異常。也就是說,調(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)致方法簽名與異常耦合,如果該方法是重寫父類的方法,則該方法拋出的異常還會(huì)受到被重寫方法所拋出異常的限制

在大部分時(shí)候推薦使用Runtime異常,而不使用Checked異常。尤其當(dāng)程序需要自行拋出異常時(shí),使用Runtime異常更加簡潔。當(dāng)使用Runtime異常時(shí),程序無須在方法中聲明拋出Checked異常,一旦發(fā)生了自定義錯(cuò)誤,程序只管拋出Runtime異常即可

使用throw拋出異常

當(dāng)程序出現(xiàn)錯(cuò)誤時(shí),系統(tǒng)會(huì)自動(dòng)拋出異常;Java允許程序自行拋出異常,自行拋出異常使用throw語句來完成。

拋出異常

throw語句可以單獨(dú)使用,throw語句拋出的不是異常類,而是一個(gè)異常實(shí)例,而且每次只能拋出一個(gè)異常實(shí)例。throw語句的語法格式如下:

throw ExceptionInsantance;

當(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語句拋出的異常是Checked異常,則該throw語句要么處于try塊里,顯式捕獲該異常,要么放在一個(gè)帶throws聲明拋出的方法中,即把該異常交給該方法的調(diào)用者處理;如果throw語句拋出的異常是Runtime異常,則該語句無須放在try塊里,也無須放在帶throws聲明拋出的方法中;程序既可以顯式使用try…catch來捕獲并處理該異常,也可以完全不理會(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挠杏眯畔ⅰK栽谶x擇拋出異常時(shí),應(yīng)該選擇合適的異常類,從而可以明確地描述該異常情況。在這種情形下,應(yīng)用程序常常需要拋出自定義異常

自定義異常都應(yīng)該繼承Exception基類,如果希望自定義Runtime異常,則應(yīng)該繼承RuntimeException基類。定義異常類時(shí)通常需要提供兩個(gè)構(gòu)造器:一個(gè)是無參數(shù)的構(gòu)造器;另一個(gè)是帶一個(gè)字符串參數(shù)的構(gòu)造器,這個(gè)字符串將作為該異常對(duì)象的描述信息(也就是異常對(duì)象的getMessage()方法的返回值)

public class AuctionException extends Exception {// 無參構(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è)方法無法完全處理該異常,必須由幾個(gè)方法協(xié)作才可以完全處理該異常。也就是說,在異常出現(xiàn)的當(dāng)前方法中,程序只對(duì)異常進(jìn)行部分處理,還有些處理需要在該方法的調(diào)用者中才能完成,所以應(yīng)該再次拋出異常,讓該方法的調(diào)用者也能捕獲到異常

為了實(shí)現(xiàn)這種通過多個(gè)方法協(xié)作處理同一異常的情形,可以在catch塊中結(jié)合throw語句來完成

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("競拍價(jià)必須是數(shù)值,"+ "不能包含其他字符!");}if (initPrice > d){throw new AuctionException("競拍價(jià)比起拍價(jià)低,"+ "不允許競拍!");}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)需要通過日志來記錄異常發(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ì)跨層訪問。下圖顯式了這種具有分層結(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){//把原始異常記錄下來,留個(gè)管理員...//下面異常中的message就是向用戶的提示throw new SalException("訪問底層數(shù)據(jù)庫出現(xiàn)異常");}catch(Exception e){//把原始異常記錄下來,留個(gè)管理員....//下面異常中的message就是向用戶的提示throw new SalException("系統(tǒng)出現(xiàn)未知異常");} }

這種把捕獲一個(gè)異常然后接著拋出另一個(gè)異常,并把原始異常信息保存下來是一種典型的鏈?zhǔn)教幚?#xff08;職責(zé)鏈模式),也稱為“異常鏈”

所有的Throwable子類在構(gòu)造器中都可以接受一個(gè)cause對(duì)象作為參數(shù)。這個(gè)cause就用來表示原始異常,這樣可以把原始異常傳遞給新的異常,使得即使在當(dāng)前位置創(chuàng)建并拋出了新的異常,你也能通過這個(gè)異常鏈追蹤到異常最初發(fā)生的位置。如果我們希望上面SalException可以追蹤到最原始的異常信息。則可以將該方法改寫如下

public calSal throws SalException {try{//實(shí)現(xiàn)結(jié)算工資的業(yè)務(wù)邏輯....}catch(SQLException sqle){//把原始異常記錄下來,留個(gè)管理員...//下面異常中的message就是向用戶的提示throw new SalException(sqle);}catch(Exception e){//把原始異常記錄下來,留個(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ù)的方法,所以可以采用如下代碼來定義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é)果,開發(fā)者可以找到異常的源頭,并跟蹤到異常一路觸發(fā)的過程

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方法開始觸發(fā),傳到secondMethod方法,再傳到firstMethod方法,最后傳到main方法,在main方法終止,這個(gè)過程就是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)

只要異常沒有被完全捕獲(包括異常沒有捕獲,或異常被處理后重新拋出了新異常),異常從發(fā)生異常的方法逐漸向外傳播,首先傳給該方法的調(diào)用者,該方法調(diào)用者再次傳給其調(diào)用者...直至最后傳到main方法,如果main方法依然沒有處理該異常,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é)束(如果該異常沒有得到處理,將會(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)

不要過度使用異常

過度使用異常主要有兩個(gè)方面:

  • 把異常和普通錯(cuò)誤混淆在一起,不再編寫任何錯(cuò)誤處理代碼,而是以簡單地拋出異常來代替所有的錯(cuò)誤處理

  • 使用異常處理來代替流程控制

異常處理機(jī)制的初衷是將不可預(yù)期的處理代碼和正常的業(yè)務(wù)邏輯處理代碼分離,因此絕不要使用異常處理來代替正常的業(yè)務(wù)邏輯判斷。另外,異常機(jī)制的效率比正常的流程控制效率差,所以不要使用異常處理來代替正常的程序流程控制

異常只應(yīng)該用于處理非正常的情況,不要使用異常處理來代替正常的流程控制。對(duì)于一些完全可預(yù)知,而且處理方式清楚的錯(cuò)誤,程序應(yīng)該提供相應(yīng)的錯(cuò)誤處理代碼,而不是將其籠統(tǒng)地稱為異常

不要使用過于龐大的try塊

正確的做法是,把大塊的try塊分割成多個(gè)可能出現(xiàn)異常的程序段落,并把它們放在單獨(dú)的try塊中,從而分別捕獲并處理異常

避免使用Catch All語句

所謂Catch All語句指的是一種異常捕獲模塊,它可以處理程序發(fā)生的所有可能異常

try {// code here with checked exceptions } catch (Throwable t) {//exception handlert.printStackTrace(); }

這種處理方式有如下兩點(diǎn)不足之處:

  • 所有異常都采用相同的處理方式,這將導(dǎo)致無法對(duì)不同異常分情況處理,如果要分情況處理,則需要在catch塊中使用分支語句進(jìn)行控制,這是得不償失的做法。

  • 這種捕獲方式可能將程序中的錯(cuò)誤、Runtime異常等可能導(dǎo)致程序終止的情況全部捕獲到,從而 “壓制”了異常。如果出現(xiàn)了一些“關(guān)鍵”異常,那個(gè)異常也會(huì)被“靜悄悄”地忽略

不要忽略捕獲到的異常

通常建議對(duì)異常進(jìn)行適當(dāng)措施:

  • 處理異常。對(duì)異常采用合適的修補(bǔ),然后繞過異常發(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語句來捕獲該異常,直接使用throws聲明拋出該異常,讓上層調(diào)用者來負(fù)責(zé)處理該異常

總結(jié)

以上是生活随笔為你收集整理的Java 异常处理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。