作為一個面向對象編程的程序員對於 下面的一句一定非常熟悉:
復制代碼
try
{
// 代碼塊
}
catch(Exception e)
{
// 異常處理
}
finally
{
// 清理工作
}
復制代碼
就是面向對象中最最常見的異常處理程序,而且甚至我們會莫名其妙的被編譯器要求加上這個模塊,甚至我們自己也不知道捕捉到異常該怎麼處理。。。
為什麼要有異常
其實這個問題不用多說,程序員都知道,簡單總結為一句話:就是為了增強程序健壯性呗,比如下面的代碼:
Class DenominatorZeroException extends Exception{}
public double division(int numerator,int denominator) throws DenominatorZeroException
{
double result;
try
{
if(denominator == 0)
throw new DenominatorZeroException();
else
result = numerator/denominator;
}
catch(DenominatorZeroException e)
{
e.printStackTrace();
return -1;
}
return result;
這段代碼很簡單,就是為了預防除法中發生分母為0的情況,為了增強代碼的健壯性,我聲明了一個自定義的異常名為:DenominatorZeroException,這個異常繼承自所有異常的根類Exception,當代碼發現分母為0的時候就將new一個異常然後跑出,當catch捕捉到這個異常後,則返回一個預先定義好的標志-1;否則正常返回除法結果。
其實在Java語言中,按照我自己看書和平時編程的理解,異常的作用可以概括為以下兩點:
增強程序健壯性。當遇到異常(為什麼叫異常而不是錯誤,就是說在當前編程環境下,出現這種情況很奇怪,不正常,無法解決)我們可以捕獲它,然後有兩種選擇:一是就像上面的代碼一樣,我們知道異常的原因,然後進行相應的處理,這樣並不會影響程序的正常執行;二是我們並不知道捕獲這個異常該怎麼處理,我們就可以利用Java的異常鏈可以將這個異常拋給上一級代碼或者直接拋給控制台(就像上面的代碼e.printStackTrace()打印出棧軌跡或者利用異常說明加在主函數入口處)。
報告。Java程序員可能會發現當我們程序遇到bug停止的時候,所有報告的錯誤信息都是以異常的形式產生的,這樣統一的形式使得程序員在編寫程序時不必考慮如果程序運行出錯怎麼辦,Java會做好的,出錯會向你報告而且絕對不會遺漏(除了異常“吞咽”,稍後說明),程序員就可以專心設計和實現自己的代碼了。
throw關鍵字
我們要將一個異常跑出就需要throw關鍵字,其實在某種程度上,我們可以將throw和return進行一個類比,因為當我們使用throw拋出異常時就會跳出當前的方法或者作用域,這與return是非常相似的。但是一定不能混淆,因為return關鍵字返回的“地點”一般就是主調函數處然而throw拋出異常,而捕獲異常的這個“地點”可能距離現有函數的地址很遠。
Class DenominatorZeroException extends Exception{}
Class AnotherException extends Exception
{
public AnotherException(String s){super(s);}
}
public double division(int numerator,int denominator) throws DenominatorZeroException, AnotherException
{
double result;
try
{
if(denominator == 0)
throw new DenominatorZeroException();
else
result = numerator/denominator;
}
catch(DenominatorZeroException e)
{
throw e;
/*或者*/
throw new RuntimeException(e);
/*或者*/
AnotherException ae = new AnotherException("from division");
ae.initCause(new DenominatorZeroException());
throw ae;
}
return result;
}
還是上面除法的例子,我想做點說明:
當我們在catch中捕獲到一個異常不知道怎麼處理時,可以利用throw直接再將這個異常拋出;
同樣我們也可以直接將這個異常拋給RuntimeException,讓它直接拋出運行時異常(就是現實在控制台的錯誤信息);
然而上面兩種方式有一個問題就是當我們再次拋出異常時,我們最一開始發生異常的位置就會丟失,所以我們利用initCause將原先的異常加入,並且在異常信息中也添加了"from division"
解釋異常“吞噬”,就是捕獲了異常什麼都不做,也不拋出,那麼這樣很危險,因為找不到錯誤信息了。
異常說明throws
我們在調用Java的庫函數的時候肯定會遇到這種情況(尤其是IO操作的地方):就是調用了一個函數,然後編譯器報錯,給出的解決方案是要麼加入try,catch塊,要麼就在主調函數的後面加上throws,並且後面跟上可能拋出的異常。這裡後者就是所謂的異常說明。
為什麼要有異常說明:這主要是編寫類的程序員要將方法可能會拋出的異常告知客戶端程序員。
格式:方法名() throws 所有潛在異常類型列表
異常說明將Java中的異常分為了兩類,一類是被檢查的異常,即Exception及所有繼承自它的異常;另一類是不受檢查的異常,即RuntimeException,即運行時異常。怎麼理解呢?
說白了就是,被檢查的異常只要你在函數中要用到throw拋出異常或者說你調用的函數利用了throw拋出了異常,那麼你就必須在函數後面加上throws關鍵字並在後面列出所有可能拋出的異常;而不受檢查的異常就是你拋出它的時候不用做特別說明,就像上面除法的例子一樣。
這種自頂向下的約束,可以保證Java代碼一定水平的異常正確性。但是這裡是有爭議的,有些人認為這樣好,但有些人認為這樣會影響程序員的編程效率,因為有時候你根本就不知道你捕捉的是什麼異常,也不知道該怎麼處理,但是編譯器會強制要求你加上這些模塊。
finally關鍵字
finally關鍵字常用數據庫的哥們肯定懂,一般我們在finally裡要關閉數據庫連接或者做一些清理工作。關於finally我想說兩點有趣的事情:
一、保證完成任務
為什麼這麼說finally關鍵字呢,就是因為無論在try語句中執行了什麼命令,finally中的語句塊一定會執行(這就確保了有些必要的清理工作),如:
public class MultipleReturns
{
public static void f(int i)
{
System.out.println("Initialization that requires cleanup");
try
{
System.out.println("Point 1");
if (i == 1)
return;
System.out.println("Point 2");
if (i == 2)
return;
System.out.println("Point 3");
if (i == 3)
return;
System.out.println("End");
return;
}
finally
{
System.out.println("Performing cleanup");
}
}
public static void main(String[] args)
{
for (int i = 1; i <= 4; i++)
f(i);
}
}
大家執行這段代碼會驚奇的發現,即使try中代碼調用了return命令但是finally也一定會執行。
二、嵌套try
public class Cleanup
{
public static void main(String[] args)
{
try
{
InputFile in = new InputFile("Cleanup.java");
try
{
String s;
int i = 1;
while ((s = in.getLine()) != null)
; // Perform line-by-line processing here...
}
catch (Exception e)
{
System.out.println("Caught Exception in main");
e.printStackTrace(System.out);
}
finally
{
in.dispose();//自定義
}
}
catch (Exception e)
{
System.out.println("InputFile construction failed");
}
}
} /*
* Output: dispose() successful
*/// :~
復制代碼
對於這個例子,我們看出我們要創建InputFile對象,當我們成功創建它時需要用dispose方法對其進行清理,然而如果失敗時我們並不需要對其進行清理。倘若只有一個不使用嵌套try塊,那麼不管怎樣finally都會執行。所以為了避免這種情況,上面的嵌套try語句就起到了作用。