一、理論講解
作爲一個面向對象編程的程序員對於 下面的一句一定非常熟悉:
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語言中,按照我自己看書和平時編程的理解,異常的作用可以概括爲以下兩點:
1、增強程序健壯性。
當遇到異常(爲什麼叫異常而不是錯誤,就是說在當前編程環境下,出現這種情況很奇怪,不正常,無法解決)我們可以捕獲它,然後有兩種選擇:一是就像上面的代碼一樣,我們知道異常的原因,然後進行相應的處理,這樣並不會影響程序的正常執行;二是我們並不知道捕獲這個異常該怎麼處理,我們就可以利用Java的異常鏈可以將這個異常拋給上一級代碼或者直接拋給控制檯(就像上面的代碼e.printStackTrace()打印出棧軌跡或者利用異常說明加在主函數入口處)。
2、報告。
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"
備註:
1、解釋異常"吞噬",就是捕獲了異常什麼都不做,也不拋出,那麼這樣很危險,因爲找不到錯誤信息了。
2、異常說明throws
我們在調用Java的庫函數的時候肯定會遇到這種情況(尤其是IO操作的地方):就是調用了一個函數,然後編譯器報錯,給出的解決方案是要麼加入try,catch塊,要麼就在主調函數的後面加上throws,並且後面跟上可能拋出的異常。這裏後者就是所謂的異常說明。
爲什麼要有異常說明:這主要是編寫類的程序員要將方法可能會拋出的異常告知客戶端程序員。
三、實例驗證
1、
package test.s;
public class yichang {
public static void main(String[] args) throws Exception{
try{
double a=aa();
System.out.println(a);
}catch(Exception e){
e.printStackTrace();
}
}
public static double aa() throws Exception{
double b = 0;
try{
b=1/0;
}catch(Exception e){
throw new Exception(e.getMessage());
}
return b;
}
}
輸出:
java.lang.Exception: / by zero
at test.s.yichang.aa(yichang.java:18)
at test.s.yichang.main(yichang.java:6)
說明:這算是比較正常的異常寫法。aa()方法拋出異常,mian方法捕獲異常,並打印出異常原因。
2,
package test.s;
public class yichang {
public static void main(String[] args) throws Exception{
try{
double a=aa();
System.out.println(a);
}catch(Exception e){
}
}
public static double aa() throws Exception{
double b = 0;
try{
b=1/0;
}catch(Exception e){
throw new Exception(e.getMessage());
}
return b;
}
}
沒有輸出;
說明:這個跟1的區別是main方法捕獲aa傳來的異常後沒有將異常打印出來,所以沒有任何輸出。
3,
package test.s;
public class yichang {
public static void main(String[] args) throws Exception{
try{
double a=aa();
System.out.println(a);
}catch(NullPointerException e){
e.printStackTrace();
}
}
public static double aa() throws Exception{
double b = 0;
try{
b=1/0;
}catch(Exception e){
throw new Exception(e.getMessage());
}
return b;
}
}
輸出:
Exception in thread "main" java.lang.Exception: / by zero
at test.s.yichang.aa(yichang.java:18)
at test.s.yichang.main(yichang.java:6)
說明:在主方法中的catch(nullPointerException e)是空指針異常。而aa()方法拋出來的異常是ArithmeticException,所以main方法雖然用try catch把aa()方法包裹起來,但是並沒有捕獲改異常。控制檯打印的是java自己處理打印出來的異常。
效果跟下面的代碼是一樣的:也就是main方法中不用try catch
package test.s;
public class yichang {
public static void main(String[] args) throws Exception{
double a=aa();
System.out.println(a);
}
public static double aa() throws Exception{
double b = 0;
try{
b=1/0;
}catch(Exception e){
throw new Exception(e.getMessage());
}
return b;
}
}
4,
package test.s;
public class yichang {
public static void main(String[] args) throws Exception{
try{
double a=aa();
System.out.println(a);
}catch(NullPointerException e){
e.printStackTrace();
}
}
public static double aa() throws Exception{
double b = 0;
try{
b=1/0;
}catch(NullPointerException e){
throw new NullPointerException(e.getMessage());
}
return b;
}
}
輸出:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at test.s.yichang.aa(yichang.java:16)
at test.s.yichang.main(yichang.java:6)
說明這種是catch(NullPointerException e),在aa方法中只能捕獲空指針異常,但是b=1/0報的是算術異常,因此也是無法捕獲的。使用debug跑程序會發現程序運到b=1/0就打印異常結束程序了。
因此同以下代碼:
package test.s;
public class yichang {
public static void main(String[] args){
double a=aa();
System.out.println(a);
}
public static double aa() {
double b = 0;
b=1/0;
return b;
}
}
5,
package test.s;
public class yichang {
public static void main(String[] args) throws Exception{
try{
double a=aa();
System.out.println(a);
}catch(NullPointerException e){
e.printStackTrace();
}
}
public static double aa() throws Exception{
double b = 0;
try{
b=1/0;
}catch(ArithmeticException e){
throw new ArithmeticException(e.getMessage());
}
return b;
}
}
輸出:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at test.s.yichang.aa(yichang.java:18)
at test.s.yichang.main(yichang.java:6)
說明:這中情況也很明顯了。aa方法中的try catch 能捕獲異常,但是mian方法中的try catch不行
6,最準確的情況
package test.s;
public class yichang {
public static void main(String[] args) throws Exception{
try{
double a=aa();
System.out.println(a);
}catch(ArithmeticException e){
e.printStackTrace();
}
}
public static double aa() throws Exception{
double b = 0;
try{
b=1/0;
}catch(ArithmeticException e){
throw new ArithmeticException(e.getMessage());
}
return b;
}
}
輸出:
java.lang.ArithmeticException: / by zero
at test.s.yichang.aa(yichang.java:18)
at test.s.yichang.main(yichang.java:6)
說明:因爲知道aa方法拋出的異常是ArithmeticException,所以準確定位第一層異常捕獲。然後在main方法中也精確捕獲到aa方法拋來的算術異常。
總結:
正確使用try catch 異常,try 不是能吃掉所有的異常,必須要在catch中使用正確的異常才能捕獲。但是在實際開發中,很難精確的捕獲可能存在的異常。因此我們大多使用第一種情況,exception是所有異常的父類,能捕獲到所有的異常。
新增:對於方法套嵌層級很多的,如果在最外層的方法被try catch,那麼無論多少層級,最後都會被最外層的try catch捕獲到,比如說在實際工作中我們經常會看到這樣的代碼,最外層的方法被try catch,如果有個方法出現空指針異常,那麼最後打印的信息會是最外層catch輸出的錯誤說明。