java中的異常處理機制

異常處理:

java中的異常處理機制主要依賴於try,catch,finally,throw,throws五個關鍵字。其中,

try關鍵字後緊跟一個花括號括起來的代碼塊(花括號不可省略)簡稱爲try塊。裏面放置可能發生異常的代碼。

catch後對應異常類型和一個代碼塊,用於表明該catch塊用於處理這種類型的代碼塊。多個catch塊後還可以跟一個finally塊。

finally塊用於回收再try塊裏打開的物理資源,異常機制會保證finally塊總被執行。

throws關鍵字主要在方法簽名中使用,用於聲明該方法可能拋出的異常

throw用於拋出一個實際的異常,throw可以單獨作爲語句使用,拋出一個具體異常對象。

java異常分爲兩種:Checked異常和Runtime異常。

java認爲Checked異常都是可以在編譯階段被處理的異常,所以它強制程序處理所有的Checked異常,而Runtime異常則無需處理。

Checked異常可以提醒程序員需要處理所有可能發生的異常,但Checked異常也帶來一些繁瑣之。

對於錯誤處理機制,主要有如下的兩個缺點:

1.無法窮舉所有異常情況:因爲人類的知識是有限的,異常情況總比可以考慮到的情況多,總有漏網之魚

2.錯誤處理代碼和業務實現代碼混雜嚴重影響程序的可讀性,會增加程序維護的難度。

 

java的異常處理機制:實現將“業務功能代碼”和“錯誤處理代碼”的分離!

1.使用try...catch捕獲異常

java提出了一種假設,如果程序可以順利完成,那麼一切正常,把系統的業務實現代碼放在try塊中定義,所有的異常處理邏輯放在catch塊中進行處理。

即:try{

//業務實現代碼

...

}

catch(Exception e){

輸入不合法

}

上面的格式中try塊和catch塊後的{...}都是不可以省略的!

執行步驟:

1.如果執行try塊中的業務邏輯代碼時出現異常,系統自動生成一個異常對象,該異常對象被提交給java運行環境,這個過程稱爲拋出(throw)異常。

2.當java運行環境收到異常對象時,會尋找能處理該異常對象的catch塊,如果找到合適的cathc塊並把該異常對象交給catch塊處理,那這個過程稱爲捕獲(catch)異常;如果java運行時環境找不到捕獲異常的catch塊,則運行時環境終止,jav程序也將退出。

注意1:不管程序代碼塊是否處於try塊中,甚至包括catch塊中代碼,只要執行該代碼時出現了異常,系統都會自動生成一個異常對象,如果程序沒有爲這段代碼定義任何catch塊,java運行環境肯定找不到處理該異常的catch塊,程序肯定在此退出。

注意2:try塊後可以有多個catch塊,try塊後使用多個catch塊是爲了針對不同異常類提供的不同的異常處理方式。當系統發生不同意外情況時,系統會生成不同的異常對象,java運行時就會根據該異常對象所屬的異常類來決定使用哪個catch塊來處理該異常。

注意3:通常情況下,如果try塊被執行一次,則try塊後只有一個catch塊會被執行,絕不可能有多個catch塊被執行,除非在循環中使用類continue開始下一次循環,下一次循環又重新運行了try塊,這纔可能導致多個catch塊被執行。

注意4:進行異常捕獲時,一定要記住先捕獲小的異常,再捕獲大的異常。

 

java異常類的繼承關係:

 java把所有非正常情況分成兩種:異常(Exception)和錯誤(Error),都是繼承自Throwable父類。

 Error錯誤:一般是指虛擬機相關的問題,如系統崩潰,虛擬機出錯誤等,這種錯誤無法恢復或不可能捕獲,將導致應用程序中斷,通常不處理。

 

訪問異常信息:

如果程序需要在catch塊中訪問異常對象的相關信息,可以通過調用catch後異常形參的方法來獲得。當java運行時決定調用某個catch塊來處理該異常對象時,會將該異常對象賦給catch塊後的異常參數,程序就可以通過該參數來獲得該異常的相關信息。

所有異常對象都包含了如下幾個常用方法:

getMessage():返回該異常的詳細描述字符串

printStackTrace():將該異常的跟蹤棧信息輸出到標準錯誤輸出

printStackTrace(PrintStream s):將該異常的跟蹤棧信息輸出到指定輸出流

getStackTrace():返回該異常的跟蹤棧信息。

 舉例如下:

public class TestException{

public static void main(String[] args)

{

try{

     FileInputStream fis=new FileInputStream("a.txt");

}

catch(IOException ioe)

{

   System.out.println(ioe.getMessage());

   ioe.printStackTrace();

}

}

}

 

使用finally回收資源

 有時候,程序在try塊裏面打開了一些物力資源(比如數據庫連接,網絡連接好磁盤文件等),這些物理資源都必須顯式回收。

因爲:java的垃圾回收機制不會回收任何的物理資源,垃圾回收機制只回收堆內存中對象所佔用的內存。

 

問題1:那麼在哪邊回收這些物理資源呢?

答: 在finally塊中,因爲如果try塊的某條語句引起一場,該語句後的其他語句通常不會被執行,那將導致位於該語句後的資源回收語句得不到執行;如果在 catch塊裏進行資源回收,但catch塊完全有可能得不到執行,這也將導致不能及時回收這些物理資源。所以我們不管try塊中的代碼是否出現異常,也 不管哪個catch塊會被執行,finally塊總會被執行。

那麼:java異常處理的完整語法結構如下:

try
{
     //業務實現邏輯
     ...
}
catch(SubException e)
{
     //異常處理快1
     ...
}
catch(SubException2 e)
{
     //異常處理快2
     ...
}
     ...
finally
{
    //資源回收塊
    ...
}

以上的異常處理語法結構中
注意點1:只有try塊石必須的,也就是說如果沒有try塊,則不可能有後面的catch塊和finally塊;
注意點2:catch塊和finally塊都是可選的,但catch塊和finally塊至少出現其中之一,也可以同時出現;
注意點3:可以有多個catch塊,捕獲父類異常的catch塊必須位於捕獲子類異常的後面;
注意點4:不能只有try塊,既沒有catch塊,也沒有finally塊;
注意點5:多個catch塊必須位於try塊之後,finally塊必須位於所有catch塊之後。

舉例如下:
package day0213;

import java.io.FileInputStream;
import java.io.IOException;

public class TestException {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  FileInputStream fis=null;
  try {
   fis=new FileInputStream("a.txt");
  } catch (IOException ioe) {
   System.out.println(ioe.getMessage());
   //return語句強制方法返回
   return;
   //使用exit來退出虛擬機
   //System.exit(1);
  }finally{
   //關閉磁盤文件,回收資源
   if (fis!=null) {
    try {
     fis.close();
    } catch (IOException ioe) {
     ioe.printStackTrace();
    }
   }
   System.out.println("程序已經執行了finally裏德資源回收");
  }
 }

}
運行程序結果:
a.txt (系統找不到指定的文件。)
程序已經執行了finally裏德資源回收

如果將catch塊中的最後兩句註釋放入程序,那麼結果爲:a.txt (系統找不到指定的文件。)

 以上兩種情況顯示:除非在try塊或者catch塊中調用了退出虛擬機的方法(即System.exit(1);),否則不管在try塊、catch塊中執行怎樣的代碼,出現怎樣的情況,異常處理的finally塊總是會被執行的。不過,一般情況下,不要再finally塊中使用renturn或throw等導致方法終止的語句,因爲一旦使用,將會導致try塊、catch塊中的return、throw語句失效。

例如:

package day0213;

public class TestException1 {

 public static boolean test() {
  try {
   return true;
  } finally {
   return false;
  }
 }

 public static void main(String[] args) {
  boolean a = test();
  System.out.println(a);
 }

}

運行結果:false

以上的小程序說明:在finally塊中定義了一個renturn false語句,這將導致try塊中的return true 失去作用!

總結一下這個小問題:

當 程序執行try塊,catch塊時遇到return語句或者throw語句,這兩個語句都會導致該方法立即結束,所以系統並不會立即執行這兩個語句,而是 去尋找該異常處理流程中的finally塊,如果沒有finally塊,程序立即執行return語句或者throw語句,方法終止。如果有 finally塊,系統立即開始執行finally塊,只有當finally塊執行完成後,系統纔會再次跳回來執行try塊、catch塊裏的 return或throw語句,如果finally塊裏也使用了return或throw等導致方法終止的語句,則finally塊已經終止了方法,不用 再跳回去執行try塊、catch塊裏的任何代碼了。

綜上:儘量避免在finally塊裏使用return或throw等導致方法終止的語句,否則可能出現一些很奇怪的情況!

異常處理的嵌套

例 如catch塊中再次包含了一個完整的異常處理流程,這種在try塊,catch塊或finally塊中包含完整的異常處理流程的情形稱爲異常處理的嵌 套。異常處理流程的代碼可以放在任何可執行代碼的地方,因此完整的異常處理流程既可放在try塊,也可放在catch塊,也可放在finally塊裏。

嵌套的深度沒有很明確的限制,通常沒有必要寫層次太深的嵌套異常處理,會導致程序可讀性降低。

 Checked異常和Runtime異常體系

 java異常被分爲兩大類:Checked異常和Runtime異常(運行時異常)。

所有RuntimeException類及其子類的實例被稱爲Runtime異常,不是RuntimeException類及其子類的異常實例則被稱爲Checked異常。

只有java語言提供了Checked異常,其他語言都沒有提供,java認爲Checked異常都是可以被處理(修復)的異常,所以java程序無須顯式的處理Checked異常。如果程序沒有處理Checked異常,該程序在編譯時就會發生錯誤,無法通過編譯。

Checked異常的處理方式:

①:當方法明確知道如何處理異常,程序應該使用try...catch塊來捕獲該異常,然後在對應的catch塊中修補該異常。

②:當方法不知道如何處理異常,應該在定義該方法時聲明拋出該異常。

Runtime異常無須顯式聲明拋出,如果程序需要捕捉Runtime異常,也可以使用try...catch塊來捕獲Runtime異常。

問題是:大部分的方法總是不能明確知道如何處理異常,這就只能聲明拋出異常了。

使用throws拋出異常

使用throws拋出異常的思路是:當前方法不知道如何處理這種類型的異常,該異常應該由上一級調用者處理,如果main方法也不知道應該如何處理這種類型的異常,也可以使用使用throws聲明拋出異常,該異常將交給JVM來處理。

JVM對異常的處理方法:打印異常跟蹤棧的信息,並終止程序運行,所以有很多程序遇到異常後自動結束。

使用throws拋出異常的格式:

throws聲明的拋出的語法格式緊跟在方法之後,可以聲明多個異常類,多個異常類之間以逗號隔開。一旦使用了throws語句聲明拋出異常,就不用再使用try...catch來捕獲異常了。

如:throws ExceptionClass1,ExceptionClass2...

注意點1:如果某段代碼調用了一個帶throws聲明的方法,該方法聲明拋出了Checked異常,這表明該方法希望它的調用者來處理該異常。那麼這段代碼要麼放在try塊中顯示捕獲該異常,要麼這段代碼處於另一個帶throws聲明拋出的方法中。

舉例如下:

 方法一:

package day0213;

import java.io.FileInputStream;
import java.io.IOException;

public class TestException2 {

//test() 方法拋出了異常,那麼test()方法的調用者要麼放在try塊中顯示捕獲該異常,要麼這段代碼處於另一個帶throws聲明拋出的方法中。

//以下爲後者的處理方法

 public static void test() throws IOException {
 FileInputStream fis=new FileInputStream("a.txt");
 }

 public static void main(String[] args) throws Exception {
       test();
 }

}

 方法二:

package day0213;

import java.io.FileInputStream;
import java.io.IOException;

public class TestException2 {
   
 public static void test() throws IOException {
 FileInputStream fis=new FileInputStream("a.txt");
 }

 public static void main(String[] args) {
       try {
  test();
 } catch (IOException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
 }
 }

}

使 用throws聲明拋出異常時有一個限制:就是方法重寫時的“兩小”中的一條規則:子類方法聲明拋出的異常類型應該是父類方法聲明拋出的異常類型的子類或 或相等,子類方法中不允許比父類方法聲明拋出更多異常。即如果子類拋出的異常是父類拋出的異常的父類,那麼程序無法通過編譯。

因爲Checked異常存在一些不便之處,大部分情況,可以使用Runtime異常,如果程序需要在合適的地方捕獲異常,並對異常進行處理,程序一樣可以用try...catch捕獲Runtime異常。

使用throw拋出異常

當程序出現錯誤時,系統會自動拋出異常,另外,java也允許程序自行拋出異常,自行拋出異常使用throw語句完成!

拋出異常:

如果需要在程序中自行拋出異常,應使用throw語句,throw語句可以單獨使用,throw語句拋出的不是異常類,而是一個異常實例,而且每次只能拋出一個異常實例。throw語句的格式如下:throw ExceptionInstance;

throw語句拋出異常的兩種情況:

1.當throw語句拋出的異常是Checked異常,則該throw語句要麼處於try塊裏顯式捕獲該異常,要麼放在一個帶throws聲明拋出的方法中,即把異常交給方法的調用者處理。

2.當throw語句拋出的異常是Runtime異常,則該語句無須放在try塊內,也無須放在帶throws聲明拋出的方法中,程序既可以顯式使用try...catch來捕獲並處理該異常,也可以完全不理會該異常,把該異常交給方法的調用者處理。

舉例如下:

package day0213;

public class TestException3 {

 public static void throwChecked(int a) throws Exception {
  if (a < 0) {
   /**
    * 自行拋出Exception異常
    * 改代碼必須處於try塊裏,或處於帶throws聲明的方法中
    */
   throw new Exception("a的值大於0,不符合要求");
  }
 }

 public static void throwRuntime(int a) {
  if (a < 0) {
   /**
    * 自行拋出RuntimeException異常,既可以顯式捕獲該異常
    * 也可以完全不用理會該異常,把該異常交給方法的調用者處理
    */
   throw new RuntimeException("a的值大於0,不符合要求");
  }else {
   System.out.println("a的值爲:"+a);
  }
 }

 public static void main(String[] args) {
  try {
   /**
    * 此處調用了帶throws聲明的方法,必須顯示捕獲該異常(使用try...catch)
    * 否則,要在main方法中再次聲明拋出
    */
   throwChecked(-3);
  } catch (Exception e) {
   System.out.println(e.getMessage());
  }
  throwRuntime(3);
 }

}

由上面的代碼顯式:自行拋出Runtime異常比自行拋出Checked異常的靈活性更好。

自定義異常類

用戶自定義異常都應該繼承Exception基類,如果希望自定義Runtime異常,則應該繼承RuntimeException基類。

應以異常類通常需要提供兩種構造器:一個是無參數的構造器,另一個是帶一個字符串的構造器,這個字符串將作爲該異常對象的詳細說明(也就是異常對象的getMessage方法的返回值)。

 例子如下:

package day0213;

public class TestException4 extends Exception {

 public TestException4() {

 }

 public TestException4(String msg) {
  super(msg);
 }

 }

catch和throw同時使用

前面已有兩種異常處理方法:

1.在異常出現的方法內捕獲並處理,方法的調用者將不能再次捕獲該異常。

2.該方法簽名中聲明拋出該異常,將該異常完全交給方法調用者處理。

但是在實際應用中往往需要更復雜的處理方式,即異常出現的當前方法中,程序只對異常進行部分處理,還有些處理需要在該方法的調用者中才能完成,所以應該再次拋出異常,可以讓該方法的調用者也能捕獲到異常。

爲了實現這種靠多個方法協作處理同一個異常的情形,可以通過catch塊中結合throw來完成。

舉例catch和throw同時使用的例子:

package day0213;

public class TestException4 {
 // 以下AuctionException這個異常是自定義的異常類
 private double initPrice = 30.0;

 public void bid(String bidPrice) throws AuctionException {
  double d = 0.0;
  try {
   d = Double.parseDouble(bidPrice);
  } catch (Exception e) {
   e.printStackTrace();
   throw new AuctionException("競拍價必須是數值,不能包含其他字符!");
  }
  if (initPrice > d) {
   throw new AuctionException("競拍價比起拍價低,不允許競拍!");
  }
  initPrice = d;
 }

 public static void main(String[] args) {
  TestException4 ta = new TestException4();
  try {
   ta.bid("df");
  } catch (AuctionException ae) {
   // TODO: handle exception
   System.err.println(ae.getMessage());
  }
 }
}

catch和throw同時使用來處理異常的方法是在大型企業中比較常用的。

java的異常跟蹤棧

異常對象的printStackTrace方法用於打印異常的跟蹤棧信息,根據printStackTrace方法的輸出結果,我們可以找到異常的源頭,並跟蹤到異常一路觸發的過程。

雖然printStackTrace()方法可以很方便地追蹤異常的發生狀況,可以用它來調試,但是在最後發佈的程序中,應該避免使用它。而應該對捕獲的異常進行適當的處理,而不是簡單的將信息打印出來。

總之,要合理使用異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章