淺談java異常[Exception]

一. 異常的定義java

在《java編程思想》中這樣定義 異常:阻止當前方法或做用域繼續執行的問題。雖然java中有異常處理機制,可是要明確一點,決不該該用"正常"的態度來看待異常。絕對一點說異常就是某種意義上的錯誤,就是問題,它可能會致使程序失敗。之因此java要提出異常處理機制,就是要告訴開發人員,你的程序出現了不正常的狀況,請注意。程序員

記得當初學習java的時候,異常老是搞不太清楚,不知道這個異常是什麼意思,爲何會有這個機制?可是隨着知識的積累逐漸也對異常有一點感受了。舉一個例子來講明一下異常的用途。編程

 

public  class  Calculator {
     public  int  devide( int  num1, int  num2) {
         //判斷除數是否爲0
         if (num2 == 0 ) {
             throw  new  IllegalArgumentException( "除數不能爲零" );
         }
         
         return  num1/num2;
     }
}

 

看一下這個類中關於除運算的方法,若是你是新手你可能會直接返回計算結果,根本不去考慮什麼參數是否正確,是否合法(固然能夠原諒,誰都是這樣過來的)。可是咱們應儘量的考慮周全,把可能致使程序失敗的"苗頭"扼殺在搖籃中,因此進行參數的合法性檢查就頗有必要了。其中執行參數檢查拋出來的那個參數非法異常,這就屬於這個方法的不正常狀況。正常狀況下咱們會正確的使用計算器,可是不排除粗枝大葉把除數賦值爲0。若是你以前沒有考慮到這種狀況,而且恰巧用戶數學基礎很差,那麼你完了。可是若是你以前考慮到了這種狀況,那麼很顯然錯誤已在你的掌控之中。數組

 

二. 異常掃盲行動ide

 

今天和別人聊天時看到一個笑話:世界上最真情的相依,是你在try我在catch。不管你發神馬脾氣,我都默默承受,靜靜處理。 大多數新手對java異常的感受就是:try...catch...。沒錯,這是用的最多的,也是最實用的。個人感受就是:java異常是從"try...catch..."走來。學習

 

首先來熟悉一下java的異常體系:spa

 

Throwable 類是 Java 語言中全部錯誤或異常的超類(這就是一切皆可拋的東西)。它有兩個子類:Error和Exception。設計

 

Error:用於指示合理的應用程序不該該試圖捕獲的嚴重問題。這種狀況是很大的問題,大到你不能處理了,因此聽之任之就好了,你不用管它。好比說VirtualMachineError:當 Java 虛擬機崩潰或用盡了它繼續操做所需的資源時,拋出該錯誤。好吧,就算這個異常的存在了,那麼應該什麼時候,如何處理它呢??交給JVM吧,沒有比它更專業的了。code

 

Exception:它指出了合理的應用程序想要捕獲的條件。Exception又分爲兩類:一種是CheckedException,一種是UncheckedException。這兩種Exception的區別主要是CheckedException須要用try...catch...顯示的捕獲,而UncheckedException不須要捕獲。一般UncheckedException又叫作RuntimeException。《effective java》指出:對於可恢復的條件使用被檢查的異常(CheckedException),對於程序錯誤(言外之意不可恢復,大錯已經釀成)使用運行時異常(RuntimeException)。blog

 

咱們常見的RuntimeExcepiton有IllegalArgumentException、IllegalStateException、NullPointerException、IndexOutOfBoundsException等等。對於那些CheckedException就不勝枚舉了,咱們在編寫程序過程當中try...catch...捕捉的異常都是CheckedException。io包中的IOException及其子類,這些都是CheckedException。

 

 

 

三. 異常的使用

 

在異常的使用這一部分主要是演示代碼,都是咱們日常寫代碼的過程當中會遇到的(固然只是一小部分),拋磚引玉嗎!

 

例1. 這個例子主要經過兩個方法對比來演示一下有了異常之後代碼的執行流程。 

 

public  static  void  testException1() {
         int [] ints = new  int [] { 1 , 2 , 3 , 4  };
         System.out.println( "異常出現前" );
         try  {
             System.out.println(ints[ 4 ]);
             System.out.println( "我還有幸執行到嗎" ); // 發生異常之後,後面的代碼不能被執行
         } catch  (IndexOutOfBoundsException e) {
             System.out.println( "數組越界錯誤" );
         }
         System.out.println( "異常出現後" );
     }
     /*output:
     異常出現前
     數組越界錯誤
     4
     異常出現後
     */

 

  

public  static  void  testException2() {
         int [] ints = new  int [] { 1 , 2 , 3 , 4  };
         System.out.println( "異常出現前" );
         System.out.println(ints[ 4 ]);
         System.out.println( "我還有幸執行到嗎" ); // 發生異常之後,他後面的代碼不能被執行
     }

  

首先指出例子中的不足之處,IndexOutofBoundsException是一個非受檢異常,因此不用try...catch...顯示捕捉,可是個人目的是對同一個異經常使用不一樣的處理方式,看它會有什麼不一樣的而結果(這裏也就只能用它將就一下了)。異常出現時第一個方法只是跳出了try塊,可是它後面的代碼會照樣執行的。可是第二種就不同了直接跳出了方法,比較強硬。從第一個方法中咱們看到,try...catch...是一種"事務性"的保障,它的目的是保證程序在異常的狀況下運行完畢,同時它還會告知程序員程序中出錯的詳細信息(這種詳細信息有時要依賴於程序員設計)。

例2. 從新拋出異常

 

public  class  Rethrow {
     public  static  void  readFile(String file) throws  FileNotFoundException {
         try  {
             BufferedInputStream in = new  BufferedInputStream( new  FileInputStream(file));
         } catch  (FileNotFoundException e) {
             e.printStackTrace();
             System.err.println( "不知道如何處理該異常或者根本不想處理它,可是不作處理又不合適,這是從新拋出異常交給上一級處理" );
             //從新拋出異常
             throw  e;
         }
     }
     
     public  static  void  printFile(String file) {
         try  {
             readFile(file);
         } catch  (FileNotFoundException e) {
             e.printStackTrace();
         }
     }
     
     public  static  void  main(String[] args) {
         printFile( "D:/file" );
     }
}

 

  

異常的本意是好的,讓咱們試圖修復程序,可是現實中咱們修復的概率很小,咱們不少時候就是用它來記錄出錯的信息。若是你厭倦了不停的處理異常,從新拋出異常對你來講多是一個很好的解脫。原封不動的把這個異常拋給上一級,拋給調用這個方法的人,讓他來費腦筋吧。這樣看來,java異常(固然指的是受檢異常)又給咱們平添不少麻煩,儘管它的出發點是好的。

 

例3. 異常鏈的使用及異常丟失

定義三個異常類:ExceptionA,ExceptionB,ExceptionC

 

public  class  ExceptionA extends  Exception {
     public  ExceptionA(String str) {
         super ();
     }
}
 
public  class  ExceptionB extends  ExceptionA {
 
     public  ExceptionB(String str) {
         super (str);
     }
}
 
public  class  ExceptionC extends  ExceptionA {
     public  ExceptionC(String str) {
         super (str);
     }
}

異常丟失的狀況:

 

public  class  NeverCaught {
     static  void  f() throws  ExceptionB{
         throw  new  ExceptionB( "exception b" );
     }
 
     static  void  g() throws  ExceptionC {
         try  {
             f();
         } catch  (ExceptionB e) {
             ExceptionC c = new  ExceptionC( "exception a" );
             throw  c;
         }
     }
 
     public  static  void  main(String[] args) {
             try  {
                 g();
             } catch  (ExceptionC e) {
                 e.printStackTrace();
             }
     }
 
}
/*
exception.ExceptionC
at exception.NeverCaught.g(NeverCaught.java:12)
at exception.NeverCaught.main(NeverCaught.java:19)
*/

爲何只是打印出來了ExceptionC而沒有打印出ExceptionB呢?這個仍是本身分析一下吧!

上面的狀況至關於少了一種異常,這在咱們排錯的過程當中很是的不利。那咱們遇到上面的狀況應該怎麼辦呢?這就是異常鏈的用武之地:保存異常信息,在拋出另一個異常的同時不丟失原來的異常。

 

public  class  NeverCaught {
     static  void  f() throws  ExceptionB{
         throw  new  ExceptionB( "exception b" );
     }
 
     static  void  g() throws  ExceptionC {
         try  {
             f();
         } catch  (ExceptionB e) {
             ExceptionC c = new  ExceptionC( "exception a" );
             //異常連
             c.initCause(e);
             throw  c;
         }
     }
 
     public  static  void  main(String[] args) {
             try  {
                 g();
             } catch  (ExceptionC e) {
                 e.printStackTrace();
             }
     }
 
}
/*
exception.ExceptionC
at exception.NeverCaught.g(NeverCaught.java:12)
at exception.NeverCaught.main(NeverCaught.java:21)
Caused by: exception.ExceptionB
at exception.NeverCaught.f(NeverCaught.java:5)
at exception.NeverCaught.g(NeverCaught.java:10)
... 1 more
*/

這個異常鏈的特性是全部異常均具有的,由於這個initCause()方法是從Throwable繼承的。

例4. 清理工做

清理工做對於咱們來講是必不可少的,由於若是一些消耗資源的操做,好比IO,JDBC。若是咱們用完之後沒有及時正確的關閉,那後果會很嚴重,這意味着內存泄露。異常的出現要求咱們必須設計一種機制不論什麼狀況下,資源都能及時正確的清理。這就是finally。

 

public  void  readFile(String file) {
         BufferedReader reader = null ;
         try  {
             reader = new  BufferedReader( new  InputStreamReader(
                     new  FileInputStream(file)));
             // do some other work
         } catch  (FileNotFoundException e) {
             e.printStackTrace();
         } finally  {
             try  {
                 reader.close();
             } catch  (IOException e) {
                 e.printStackTrace();
             }
         }
     }

例子很是的簡單,是一個讀取文件的例子。這樣的例子在JDBC操做中也很是的常見。(因此,我以爲對於資源的及時正確清理是一個程序員的基本素質之一。)

Try...finally結構也是保證資源正確關閉的一個手段。若是你不清楚代碼執行過程當中會發生什麼異常狀況會致使資源不能獲得清理,那麼你就用try對這段"可疑"代碼進行包裝,而後在finally中進行資源的清理。舉一個例子:

 

public  void  readFile() {
         BufferedReader reader = null ;
         try  {
             reader = new  BufferedReader( new  InputStreamReader(
                     new  FileInputStream( "file" )));
             // do some other work
         
             //close reader
             reader.close();
         } catch  (FileNotFoundException e) {
             e.printStackTrace();
         } catch  (IOException e) {
             e.printStackTrace();
         }
     }

 

咱們注意一下這個方法和上一個方法的區別,下一我的可能習慣更好一點,及早的關閉reader。可是每每事與願違,由於在reader.close()之前異常隨時可能發生,這樣的代碼結構不能預防任何異常的出現。由於程序會在異常出現的地方跳出,後面的代碼不能執行(這在上面應經用實例證實過)。這時咱們就能夠用try...finally來改造:

 

public  void  readFile() {
         BufferedReader reader = null ;
         try  {
             try  {
                 reader = new  BufferedReader( new  InputStreamReader(
                         new  FileInputStream( "file" )));
                 // do some other work
 
                 // close reader
             } finally  {
                 reader.close();
             }
         } catch  (FileNotFoundException e) {
             e.printStackTrace();
         } catch  (IOException e) {
             e.printStackTrace();
         }
     }

及早的關閉資源是一種良好的行爲,由於時間越長你忘記關閉的可能性越大。這樣在配合上try...finally就保證萬無一失了(不要嫌麻煩,java就是這麼中規中矩)。

再說一種狀況,假如我想在構造方法中打開一個文件或者建立一個JDBC鏈接,由於咱們要在其餘的方法中使用這個資源,因此不能在構造方法中及早的將這個資源關閉。那咱們是否是就沒轍了呢?答案是否認的。看一下下面的例子:

 

public  class  ResourceInConstructor {
     BufferedReader reader = null ;
     public  ResourceInConstructor() {
         try  {
             reader = new  BufferedReader( new  InputStreamReader( new  FileInputStream( "" )));
         } catch  (FileNotFoundException e) {
             e.printStackTrace();
         }
     }
     
     public  void  readFile() {
         try  {
             while (reader.readLine()!= null ) {
                 //do some work
             }
         } catch  (IOException e) {
             e.printStackTrace();
         }
     }
     
     public  void  dispose() {
         try  {
             reader.close();
         } catch  (IOException e) {
             e.printStackTrace();
         }
     }
}

這一部分講的多了一點,可是異常確實是看起來容易用起來難的東西呀,java中仍是有好多的東西須要深挖的。

 

四. 異常的誤用

對於異常的誤用着實很常見,上一部分中已經列舉了幾個,你們仔細的看一下。下面再說兩個其餘的。

例1. 用一個Exception來捕捉全部的異常,很有"一夫當關萬夫莫開"的氣魄。不過這也是最傻的行爲。

 

public  void  readFile(String file) {
         BufferedReader reader = null ;
         Connection conn = null ;
         try  {
             reader = new  BufferedReader( new  InputStreamReader(
                     new  FileInputStream(file)));
             // do some other work
             
             conn = DriverManager.getConnection( "" );
             //...
         } catch  (Exception e) {
             e.printStackTrace();
         } finally  {
             try  {
                 reader.close();
                 conn.close();
             } catch  (Exception e) {
                 e.printStackTrace();
             }
         }
     }

 

  

從異常角度來講這樣嚴格的程序確實是萬無一失,全部的異常都能捕獲。可是站在編程人員的角度,萬一這個程序出錯了咱們該如何分辨是究竟是那引發的呢,IO仍是JDBC...因此,這種寫法很值得當作一個反例。你們不要覺得這種作法很幼稚,傻子纔會作。我在公司實習時確實看見了相似的狀況:只不過是人家沒有用Exception而是用了Throwable。

例2. 這裏就不舉例子了,上面的程序都是反例。異常是程序處理意外狀況的機制,當程序發生意外時,咱們須要儘量多的獲得意外的信息,包括髮生的位置,描述,緣由等等。這些都是咱們解決問題的線索。可是上面的例子都只是簡單的printStackTrace()。若是咱們本身寫代碼,就要儘量多的對這個異常進行描述。好比說爲何會出現這個異常,什麼狀況下會發生這個異常。若是傳入方法的參數不正確,告知什麼樣的參數是合法的參數,或者給出一個sample。

例3. 將try block寫的簡短,不要全部的東西都扔在這裏,咱們儘量的分析出到底哪幾行程序可能出現異常,只是對可能出現異常的代碼進行try。儘可能爲每個異常寫一個try...catch,避免異常丟失。在IO操做中,一個IOException也具備"一夫當關萬夫莫開"的氣魄。

五.總結

總結很是簡單,不要爲了使用異常而使用異常。異常是程序設計的一部分,對它的設計也要考究點。

相關文章
相關標籤/搜索