一篇文章解決Java異常處理
前言
與異常相關(guān)的內(nèi)容其實(shí)很早就想寫了,但由于各種原因(懶)拖到了現(xiàn)在。在大二開學(xué)前夜(今天是8.31)完成這篇博客,也算完成了暑期生活的一個(gè)小心愿。
以下內(nèi)容大多總結(jié)自《Java核心技術(shù) 卷Ⅰ》,同時(shí)也加上了一些華東師范大學(xué)陳良育老師在《Java核心技術(shù)》Mooc中所講的內(nèi)容。
一、引例
假定你希望完成一個(gè)read方法,它的作用是讀取一個(gè)文件中的內(nèi)容并進(jìn)行相關(guān)處理,如果你從未學(xué)過處理異常的方法,你可能會(huì)這樣寫:
public void read(String filename) { var in = new FileInputStream(filename); int b; while((b = in.read()) != -1) { ... } }
問題在于,F(xiàn)ileInputStream的構(gòu)造器要求傳入的filename是已經(jīng)存在的一個(gè)文件的名稱,如果文件名不存在,那么程序?qū)惓=K止,可能導(dǎo)致用戶在運(yùn)行程序期間所做的工作全部丟失,這顯然不是我們希望看到的。
正確的做法之一是增加一個(gè)異常處理機(jī)制,將控制權(quán)從產(chǎn)生錯(cuò)誤的地方轉(zhuǎn)移到能夠處理這種錯(cuò)誤的處理器中。這樣就可以盡可能的減少損失。
public void read(String filename) { try { var in = new FileInputStream(filename); int b; while((b = in.read()) != -1) { ... } } catch(IOException exception) { ...//處理錯(cuò)誤 } } //里面可能有些代碼你暫且還不明白,不過沒關(guān)系,之后會(huì)認(rèn)真講解。
二、異常的分類與類型
在Java中,每種異常都是一個(gè)類(如上例中的IOException),每個(gè)異常對象都是一個(gè)實(shí)例。
- 所有的異常都是由Throwable類繼承而來。
- Throwable類的下一層有兩個(gè)分支:Error類和Exception類
- Exception類又分解為RuntimeException(編程錯(cuò)誤導(dǎo)致的異常)與IOException(其他異常)
Error類:描述Java運(yùn)行時(shí)系統(tǒng)內(nèi)部錯(cuò)誤和資源耗盡錯(cuò)誤。 RuntimeException類:編程錯(cuò)誤。例如數(shù)組越界訪問、錯(cuò)誤的強(qiáng)制類型轉(zhuǎn)換、訪問null指針等。
IOException類:其他異常。例如打開不存在的文件等等。
對于Error異常,我們無能為力;而對于RuntimeException異常,我們要做的是預(yù)防而非處理,譬如在程序中寫好檢測數(shù)組下標(biāo)是否越界的代碼、在使用變量前檢測它是否為null;我們重點(diǎn)處理的對象是IOException異常。
鑒于上述特性,Error與RuntimeException異常在Java語言規(guī)范中被稱為非檢查型異常;IOException稱為檢查型異常。編譯器只會(huì)幫助你檢查是否為所有的檢查型異常提供了異常處理器。
三、處理檢查型異常之方法一:聲明與拋出
如果你知道一個(gè)方法會(huì)產(chǎn)生某種(或多種)檢查型異常,但又不想處理這個(gè)異常(或無法處理),可以選擇僅聲明該類異常,將異常的處理交由調(diào)用這個(gè)方法的人。
具體的操作方式為:在方法名稱后面加上 throws + 異常類型,程序中如果真的遇到了這種異常,則 throw + 異常類的對象拋出異常。
String readData(Scanner in) throws EOFException { ... while(...) { if(!in.hasNext()) { if(n < len)//遇見EOFException異常 { throw new EOFException();//拋出異常 } ... } return s; } }
聲明異常時(shí)請注意:
- 如果一個(gè)方法可能拋出多個(gè)檢查型異常,則必須列出所有的檢查型的異常類。(否則編譯器會(huì)發(fā)出一個(gè)錯(cuò)誤消息)
public void read(String filename) throws FileNotFoundException, EOFException
- 如果子類覆蓋了父類中的一個(gè)方法,則子類中該方法對異常的聲明范圍不能超過父類(即子類中覆蓋的方法要么拋出更具體的異常,要么不拋出異常)。
- 如果一個(gè)方法聲明它會(huì)拋出一個(gè)異常,那么這個(gè)方法拋出的異??赡軐儆谶@個(gè)類,也可能屬于這個(gè)類的子類。
拋出異常時(shí)請注意:
- 如果一個(gè)方法調(diào)用了拋出檢查型異常的方法,這個(gè)方法要么處理這個(gè)異常(怎么處理標(biāo)題五會(huì)詳細(xì)介紹),要么繼續(xù)傳遞這個(gè)異常(即繼續(xù)throws)。
- 如果一個(gè)方法是覆蓋了超類中的方法,并且在超類中這個(gè)方法沒有拋出異常,那么你別無選擇,必須處理所有可能發(fā)生的檢查型異常(因?yàn)樽宇愔械母采w方法拋出范圍不得超過父類)。
四、定義自己的異常類
標(biāo)題三中的處理方法雖然簡便,但有一個(gè)很大的缺點(diǎn):你必須找到一個(gè)合適的可能觸發(fā)的異常類別,才能將其拋出。于是我們想,可不可以自己定義一個(gè)異常類別,這樣即使我們找不到合適的異常類,也可以將其拋出?
通常讓自定義的類繼承于Exception類或者IOException類,并且按照習(xí)慣,任何異常類都至少需要包含兩個(gè)構(gòu)造器,一個(gè)是默認(rèn)構(gòu)造器,另一個(gè)是包含有詳細(xì)描述信息的構(gòu)造器(方便調(diào)試)。
舉例:
class FileFormatException extends IOException//自定義異常類 { public FileFormatException(){} public FileFormarException(String gripe) { super(gripe); } } String readData(BufferedReader in) throw FileFormatException//聲明自定義的異常 { ... while(...) { if(ch == -1) { if(n < len) throw new FileFormatException();//拋出自定義的異常 } } }
五、處理檢查型異常之方法二:try-catch-finally捕獲異常
現(xiàn)在讓我們回到引例上來:
public void read(String filename) { try { var in = new FileInputStream(filename); int b; while((b = in.read()) != -1) { ... } } catch(IOException exception) { ... } }
小知識(shí):“如果發(fā)生了某個(gè)異常,但沒有被捕獲,程序就會(huì)終止,并在控制臺(tái)上打印一個(gè)消息,其中包括這個(gè)異常的類型和一個(gè)堆棧軌跡?!?/p>
引例中完成的是一個(gè)最簡單的 try-catch 語句,現(xiàn)在我們來分析一下不同情況下程序相應(yīng)的執(zhí)行情況:
- 如果 try 中出現(xiàn)了一個(gè)異常,且該異常被成功捕獲(在上例中即異常類型剛好為catch中的IOException類型)。則程序會(huì)跳過 try 中其余代碼,接著執(zhí)行 catch 子句中的代碼。
- 如果 try 中出現(xiàn)了一個(gè)異常,且該異常未被成功捕獲(即異常類型與catch中的異常類型不符)。則程序會(huì)立即退出。
- 如果 try 中未出現(xiàn)任何異常,將不會(huì)執(zhí)行 catch 中的語句。
多 catch 捕獲多個(gè)異常:
在一個(gè)try語句塊中可以捕獲多個(gè)異常類型,并對每個(gè)異常類型使用一個(gè)單獨(dú)的 catch 子句。并且,自 Java 7之后,允許一個(gè) catch 子句捕獲多個(gè)異常類型。
try { ... } catch(FileNotFoundException | UnknownHostException e)//一個(gè)catch捕獲多個(gè)異常 { ... } catch(IOException e)//一個(gè) try 語句塊中多個(gè) catch 語句 { ... }
注意:
- 用一個(gè)catch捕獲多個(gè)異常時(shí),異常變量隱含為final變量,因此不允許為 e 賦于不同的值。
- 多個(gè)catch子塊存在時(shí),由于程序是由上往下依次捕捉,不允許將父類異常寫在子類的上方。
- catch語塊中允許再次拋出異常(throw語句)
try-catch-finally語句:
假定這樣一種情況,一個(gè)程序在 try 中使用了一些本地資源,而且這些資源必須在程序退出時(shí)進(jìn)行清理。如果只用try-catch方法,那么每個(gè)catch語句中都要重復(fù)書寫清理資源的代碼,顯得非常笨重且繁瑣?,F(xiàn)在,我們可以用finally語句來解決這個(gè)問題,它的特定是:不管異常有無被捕獲,finally語句中的代碼都會(huì)執(zhí)行。功能是:確保資源被清理。
示例:在finally語句中關(guān)閉輸出流
var in = new FileInputStream(...) try { //1 可能發(fā)生異常的語句 //2 } catch (IOException e) { //3 展示錯(cuò)誤信息 //4 } finally { //5 in.close();//關(guān)閉輸出流 } //6
現(xiàn)在我們來分析一下不同情況下程序相應(yīng)的執(zhí)行情況:
- try 中代碼未拋出異常。執(zhí)行1256。
- try 中代碼拋出異常并且被捕獲且 catch沒有拋出異常。執(zhí)行13456。
- try 中代碼拋出異常并且被捕獲但 catch拋出異常。執(zhí)行135。
- try 中代碼拋出異常但未被捕獲。執(zhí)行15。
總結(jié)一下,
執(zhí)行6的條件是,走完了整個(gè) try / catch塊。
執(zhí)行5的條件是,任何條件都會(huì)執(zhí)行。
注意:
- 允許一個(gè)catch塊都沒有。
- 允許try / catch / finally子塊中嵌套一個(gè)try-catch-finally塊。
- 千萬不要在finally子塊中使用改變控制流的語句!(return,throw,break,continue)。因?yàn)閒inally的執(zhí)行在try之后,整個(gè)方法返回之前,因此在finally子塊中改變控制流將會(huì)覆蓋 try 中的結(jié)果。(這個(gè)結(jié)果可以是一個(gè)return值,也可以是一個(gè)異常)。
六、額外補(bǔ)充之try-with-Resources語句
Java中存在一個(gè)AutoCloseable接口
public interface AutoCloseable { void close() throws Exception; }
假定資源屬于一個(gè)實(shí)現(xiàn)了AutoCloseable接口的類,那么處理異常時(shí)可以不需要finally子塊。因?yàn)樵撡Y源無論是正常退出或產(chǎn)生異常,都會(huì)自動(dòng)調(diào)用close方法,代替了finally子塊。
帶資源的try語句通用格式:
try(Resources res = ...) { work with Resources; }
舉例如下:
try(var in = new Scanner(...)) { while(in.hashNext()) System.out.println(in.next()); }//無論 try塊怎么樣退出,都會(huì)執(zhí)行 in.close();
這種方式使得代碼看起來更加簡潔。
請注意:
- try-with-Resources語句允許有catch / finally子句。它們會(huì)在資源關(guān)閉后再執(zhí)行。
最后的最后,希望提醒大家,捕獲異常雖然方便,但會(huì)極大的延長程序運(yùn)行的時(shí)間,因此,只在必要條件下使用異常。
總結(jié)
到此這篇關(guān)于一篇文章解決Java異常處理的文章就介紹到這了,更多相關(guān)Java異常處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java常用數(shù)字工具類 數(shù)字轉(zhuǎn)漢字(1)
這篇文章主要為大家詳細(xì)介紹了Java常用數(shù)字工具類,數(shù)字轉(zhuǎn)漢字,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05Springboot整合Jedis實(shí)現(xiàn)單機(jī)版或哨兵版可切換配置方法
這篇文章主要介紹了Springboot整合Jedis實(shí)現(xiàn)單機(jī)版或哨兵版可切換配置方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-11-11Mybatis一對多關(guān)聯(lián)關(guān)系映射實(shí)現(xiàn)過程解析
這篇文章主要介紹了Mybatis一對多關(guān)聯(lián)關(guān)系映射實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02spring?boot項(xiàng)目中如何使用nacos作為配置中心
這篇文章主要介紹了spring?boot項(xiàng)目中如何使用nacos作為配置中心問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12java 中設(shè)計(jì)模式(值對象)的實(shí)例詳解
這篇文章主要介紹了java 中設(shè)計(jì)模式(值對象)的實(shí)例詳解的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-09-09Spring?Boot自動(dòng)配置源碼實(shí)例解析
Spring Boot作為Java領(lǐng)域最為流行的快速開發(fā)框架之一,其核心特性之一就是其強(qiáng)大的自動(dòng)配置機(jī)制,下面這篇文章主要給大家介紹了關(guān)于Spring?Boot自動(dòng)配置源碼的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-08-08