一篇文章解決Java異常處理
前言
與異常相關(guān)的內(nèi)容其實很早就想寫了,但由于各種原因(懶)拖到了現(xiàn)在。在大二開學(xué)前夜(今天是8.31)完成這篇博客,也算完成了暑期生活的一個小心愿。
以下內(nèi)容大多總結(jié)自《Java核心技術(shù) 卷Ⅰ》,同時也加上了一些華東師范大學(xué)陳良育老師在《Java核心技術(shù)》Mooc中所講的內(nèi)容。
一、引例
假定你希望完成一個read方法,它的作用是讀取一個文件中的內(nèi)容并進行相關(guān)處理,如果你從未學(xué)過處理異常的方法,你可能會這樣寫:
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)存在的一個文件的名稱,如果文件名不存在,那么程序?qū)惓=K止,可能導(dǎo)致用戶在運行程序期間所做的工作全部丟失,這顯然不是我們希望看到的。
正確的做法之一是增加一個異常處理機制,將控制權(quán)從產(chǎn)生錯誤的地方轉(zhuǎn)移到能夠處理這種錯誤的處理器中。這樣就可以盡可能的減少損失。
public void read(String filename)
{
try
{
var in = new FileInputStream(filename);
int b;
while((b = in.read()) != -1)
{
...
}
}
catch(IOException exception)
{
...//處理錯誤
}
}
//里面可能有些代碼你暫且還不明白,不過沒關(guān)系,之后會認真講解。
二、異常的分類與類型
在Java中,每種異常都是一個類(如上例中的IOException),每個異常對象都是一個實例。
- 所有的異常都是由Throwable類繼承而來。
- Throwable類的下一層有兩個分支:Error類和Exception類
- Exception類又分解為RuntimeException(編程錯誤導(dǎo)致的異常)與IOException(其他異常)
Error類:描述Java運行時系統(tǒng)內(nèi)部錯誤和資源耗盡錯誤。 RuntimeException類:編程錯誤。例如數(shù)組越界訪問、錯誤的強制類型轉(zhuǎn)換、訪問null指針等。
IOException類:其他異常。例如打開不存在的文件等等。
對于Error異常,我們無能為力;而對于RuntimeException異常,我們要做的是預(yù)防而非處理,譬如在程序中寫好檢測數(shù)組下標是否越界的代碼、在使用變量前檢測它是否為null;我們重點處理的對象是IOException異常。
鑒于上述特性,Error與RuntimeException異常在Java語言規(guī)范中被稱為非檢查型異常;IOException稱為檢查型異常。編譯器只會幫助你檢查是否為所有的檢查型異常提供了異常處理器。

三、處理檢查型異常之方法一:聲明與拋出
如果你知道一個方法會產(chǎn)生某種(或多種)檢查型異常,但又不想處理這個異常(或無法處理),可以選擇僅聲明該類異常,將異常的處理交由調(diào)用這個方法的人。
具體的操作方式為:在方法名稱后面加上 throws + 異常類型,程序中如果真的遇到了這種異常,則 throw + 異常類的對象拋出異常。
String readData(Scanner in) throws EOFException
{
...
while(...)
{
if(!in.hasNext())
{
if(n < len)//遇見EOFException異常
{
throw new EOFException();//拋出異常
}
...
}
return s;
}
}
聲明異常時請注意:
- 如果一個方法可能拋出多個檢查型異常,則必須列出所有的檢查型的異常類。(否則編譯器會發(fā)出一個錯誤消息)
public void read(String filename) throws FileNotFoundException, EOFException
- 如果子類覆蓋了父類中的一個方法,則子類中該方法對異常的聲明范圍不能超過父類(即子類中覆蓋的方法要么拋出更具體的異常,要么不拋出異常)。
- 如果一個方法聲明它會拋出一個異常,那么這個方法拋出的異??赡軐儆谶@個類,也可能屬于這個類的子類。
拋出異常時請注意:
- 如果一個方法調(diào)用了拋出檢查型異常的方法,這個方法要么處理這個異常(怎么處理標題五會詳細介紹),要么繼續(xù)傳遞這個異常(即繼續(xù)throws)。
- 如果一個方法是覆蓋了超類中的方法,并且在超類中這個方法沒有拋出異常,那么你別無選擇,必須處理所有可能發(fā)生的檢查型異常(因為子類中的覆蓋方法拋出范圍不得超過父類)。
四、定義自己的異常類
標題三中的處理方法雖然簡便,但有一個很大的缺點:你必須找到一個合適的可能觸發(fā)的異常類別,才能將其拋出。于是我們想,可不可以自己定義一個異常類別,這樣即使我們找不到合適的異常類,也可以將其拋出?
通常讓自定義的類繼承于Exception類或者IOException類,并且按照習(xí)慣,任何異常類都至少需要包含兩個構(gòu)造器,一個是默認構(gòu)造器,另一個是包含有詳細描述信息的構(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)
{
...
}
}
小知識:“如果發(fā)生了某個異常,但沒有被捕獲,程序就會終止,并在控制臺上打印一個消息,其中包括這個異常的類型和一個堆棧軌跡?!?/p>
引例中完成的是一個最簡單的 try-catch 語句,現(xiàn)在我們來分析一下不同情況下程序相應(yīng)的執(zhí)行情況:
- 如果 try 中出現(xiàn)了一個異常,且該異常被成功捕獲(在上例中即異常類型剛好為catch中的IOException類型)。則程序會跳過 try 中其余代碼,接著執(zhí)行 catch 子句中的代碼。
- 如果 try 中出現(xiàn)了一個異常,且該異常未被成功捕獲(即異常類型與catch中的異常類型不符)。則程序會立即退出。
- 如果 try 中未出現(xiàn)任何異常,將不會執(zhí)行 catch 中的語句。
多 catch 捕獲多個異常:
在一個try語句塊中可以捕獲多個異常類型,并對每個異常類型使用一個單獨的 catch 子句。并且,自 Java 7之后,允許一個 catch 子句捕獲多個異常類型。
try
{
...
}
catch(FileNotFoundException | UnknownHostException e)//一個catch捕獲多個異常
{
...
}
catch(IOException e)//一個 try 語句塊中多個 catch 語句
{
...
}
注意:
- 用一個catch捕獲多個異常時,異常變量隱含為final變量,因此不允許為 e 賦于不同的值。
- 多個catch子塊存在時,由于程序是由上往下依次捕捉,不允許將父類異常寫在子類的上方。
- catch語塊中允許再次拋出異常(throw語句)
try-catch-finally語句:
假定這樣一種情況,一個程序在 try 中使用了一些本地資源,而且這些資源必須在程序退出時進行清理。如果只用try-catch方法,那么每個catch語句中都要重復(fù)書寫清理資源的代碼,顯得非常笨重且繁瑣。現(xiàn)在,我們可以用finally語句來解決這個問題,它的特定是:不管異常有無被捕獲,finally語句中的代碼都會執(zhí)行。功能是:確保資源被清理。
示例:在finally語句中關(guān)閉輸出流
var in = new FileInputStream(...)
try
{
//1
可能發(fā)生異常的語句
//2
}
catch (IOException e)
{
//3
展示錯誤信息
//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的條件是,走完了整個 try / catch塊。
執(zhí)行5的條件是,任何條件都會執(zhí)行。
注意:
- 允許一個catch塊都沒有。
- 允許try / catch / finally子塊中嵌套一個try-catch-finally塊。
- 千萬不要在finally子塊中使用改變控制流的語句?。╮eturn,throw,break,continue)。因為finally的執(zhí)行在try之后,整個方法返回之前,因此在finally子塊中改變控制流將會覆蓋 try 中的結(jié)果。(這個結(jié)果可以是一個return值,也可以是一個異常)。
六、額外補充之try-with-Resources語句
Java中存在一個AutoCloseable接口
public interface AutoCloseable
{
void close() throws Exception;
}
假定資源屬于一個實現(xiàn)了AutoCloseable接口的類,那么處理異常時可以不需要finally子塊。因為該資源無論是正常退出或產(chǎn)生異常,都會自動調(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塊怎么樣退出,都會執(zhí)行 in.close();
這種方式使得代碼看起來更加簡潔。
請注意:
- try-with-Resources語句允許有catch / finally子句。它們會在資源關(guān)閉后再執(zhí)行。
最后的最后,希望提醒大家,捕獲異常雖然方便,但會極大的延長程序運行的時間,因此,只在必要條件下使用異常。
總結(jié)
到此這篇關(guān)于一篇文章解決Java異常處理的文章就介紹到這了,更多相關(guān)Java異常處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC使用MultipartFile 實現(xiàn)異步上傳方法介紹
這篇文章主要介紹了SpringMVC使用MultipartFile 實現(xiàn)異步上傳方法介紹,涉及pom依賴的添加,配置文件的修改等具體操作代碼,需要的朋友可以了解下。2017-09-09
SpringBoot?容器刷新前回調(diào)ApplicationContextInitializer
這篇文章主要為大家介紹了SpringBoot?容器刷新前回調(diào)ApplicationContextInitializer使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12

