Java 例外處理


異常是程式中的一些錯誤,但並不是所有的錯誤都是異常,並且錯誤有時是可以避免的。

比如說,你的程式碼少了一個分號,那麼執行出來結果是提示是錯誤java.lang.Error;如果你用System.out.println(11/0),那麼你是因為你用0做了除數,會拋出java.lang.ArithmeticException的例外。

異常發生的原因有很多,通常包含以下幾個類別:

  •                 使用者輸入了非法資料。

  •                 要開啟的文件不存在。

  •                 網路通訊時連線中斷,或JVM記憶體溢位。

這些異常有的是因為使用者錯誤引起,有的是程式錯誤引起的,還有其它一些是因為物理錯誤引起的。 -

要瞭解Java異常處理是如何運作的,你需要掌握以下三種類型的例外:

  • ##檢查性例外:最具代表的檢查性異常是使用者錯誤或問題引起的異常,這是程式設計師無法預見的。例如要開啟一個不存在檔案時,一個異常就發生了,這些異常在編譯時不能被簡單地忽略。

  • 執行階段異常: 執行時期異常是可能被程式設計師避免的例外。與檢查性異常相反,運行時異常可以在編譯時被忽略。

  • 錯誤: 錯誤不是異常,而是脫離程式設計師控制的問題。錯誤在程式碼中通常被忽略。例如,當堆疊溢出時,一個錯誤就發生了,它們在編譯也檢查不到的。


Exception類別的層次

所有的例外類別是從java.lang.Exception類別繼承的子類別。

Exception類別是Throwable類別的子類別。除了Exception類別外,Throwable還有一個子類別Error 。

Java程式通常不捕獲錯誤。錯誤一般發生在嚴重故障時,它們在Java程式處理的範疇之外。

Error用來指示執行階段環境發生的錯誤。

例如,JVM記憶體溢位。一般地,程式不會從錯誤中恢復。

異常類別有兩個主要的子類別:IOException類別和RuntimeException類別。

12-130Q1234I6223.jpg

在Java 內建類別中(接下來會說明),有大部分常用檢查性和非檢查性異常。


Java 內建異常類別

Java 語言定義了一些異常類別在java.lang標準套件中。

標準運行時異常類別的子類別是最常見的異常類別。由於java.lang包是預設載入到所有的Java程式的,所以大部分從執行時期異常類別繼承而來的異常都可以直接使用。

Java根據各個類別庫也定義了一些其他的異常,下面的表中列出了Java的非檢查性異常。

# 說明
                    ArithmeticException                    當出現異常的運算條件時,拋出此異常。例如,一個整數"除以零"時,拋出此類的一個實例。
                    ArrayIndexOutOfBoundsException                    用非法索引存取數組時拋出的異常。如果索引為負或大於等於陣列大小,則該索引為非法索引。
                    ArrayStoreException                    試圖將錯誤類型的物件儲存到一個物件數組時拋出的異常。
                    ClassCastException                    當試圖將物件強制轉換為不是實例的子類別時,拋出該異常。
                    IllegalArgumentException                    拋出的異常表示向方法傳遞了一個不合法或不正確的參數。
                    IllegalMonitorStateException                    拋出的異常表示某一執行緒已經試圖等待物件的監視器,或是試圖通知其他正在等待物件的監視器而本身沒有指定監視器的執行緒。
                    IllegalStateException                    在非法或不適當的時間呼叫方法時產生的訊號。換句話說,即 Java 環境或 Java 應用程式沒有處於請求操作所要求的適當狀態。
                    IllegalThreadStateException                    執行緒沒有處於請求操作所要求的適當狀態時所拋出的例外。
                    IndexOutOfBoundsException                    指示某排序索引(例如對陣列、字串或向量的排序)超出範圍時拋出。
                    NegativeArraySizeException                    如果應用程式試圖建立大小為負的數組,則拋出該異常。
                    NullPointerException                    當應用程式試圖在需要物件的地方使用 null 時,拋出該例外
                    NumberFormatException                    當應用程式試圖將字串轉換成一種數值類型,但該字串不能轉換為適當格式時,拋出該異常。
                    SecurityException                    由安全管理器拋出的異常,指示有安全侵犯。
                    StringIndexOutOfBoundsException                    此異常由 String 方法拋出,指示索引或為負,或超出字串的大小。
                    UnsupportedOperationException                    當不支援請求的操作時,拋出該異常。

下面的表中列出了Java定義在java.lang套件中的檢查性異常類別。

##                    ClassNotFoundException                    應用程式試圖載入類別時,找不到相應的類,拋出該異常。                     CloneNotSupportedException                    當呼叫                     IllegalAccessException                    拒絕訪問一個類別的時候,拋出該異常。                     InstantiationException                    當試圖使用                    InterruptedException                    一個執行緒被另一個執行緒中斷,拋出該例外。                     NoSuchFieldException                    請求的變數不存在                    NoSuchMethodException                    請求的方法不存在

異常方法

下面的清單是Throwable 類別的主要方法:

#說明
Object 類別中的 clone 方法複製對象,但該物件的類別無法實作 Cloneable 介面時,拋出該例外。
Class 類別中的newInstance 方法建立一個類別的實例,而指定的類別物件因為是一個介面或是一個抽象類別而無法實例化時,拋出該異常。
                    1                    2                    3                    4                    5                    6

捕獲異常

使用try和catch關鍵字可以捕獲異常。 try/catch程式碼區塊放在異常可能發生的地方。

try/catch程式碼區塊中的程式碼稱為保護程式碼,使用 try/catch的語法如下:

try
{
   // 程序代码
}catch(ExceptionName e1)
{
   //Catch 块
}

Catch語句包含要擷取例外類型的宣告。當保護程式碼區塊中發生一個異常時,try後面的catch區塊就會被檢查。

如果發生的異常包含在catch區塊中,則異常會被傳遞到該catch區塊,這和傳遞一個參數到方法是一樣。

實例

下面的例子中宣告有兩個元素的一個數組,當程式碼試圖存取數組的第三個元素的時候就會拋出一個例外。

// 文件名 : ExcepTest.java
import java.io.*;
public class ExcepTest{

   public static void main(String args[]){
      try{
         int a[] = new int[2];
         System.out.println("Access element three :" + a[3]);
      }catch(ArrayIndexOutOfBoundsException e){
         System.out.println("Exception thrown  :" + e);
      }
      System.out.println("Out of the block");
   }
}

以上程式碼編譯運行輸出結果如下:

Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
Out of the block

多重擷取區塊

一個try程式碼區塊後面跟隨多個catch程式碼區塊的情況就叫多重捕獲。

多重擷取區塊的語法如下所示:

 try{
    // 程序代码
 }catch(异常类型1 异常的变量名1){
    // 程序代码
 }catch(异常类型2 异常的变量名2){
    // 程序代码
 }catch(异常类型2 异常的变量名2){
    // 程序代码
 }

上面的程式碼片段包含了3個catch區塊。

可以在try語句後面加入任意數量的catch區塊。

如果保護程式碼中發生異常,則異常被拋給第一個catch區塊。

如果拋出異常的資料類型與ExceptionType1匹配,它在這裡就會被捕獲。

如果不匹配,它會被傳遞給第二個catch區塊。

如此,直到異常被捕獲或通過所有的catch區塊。

實例

該實例展示了怎麼使用多重try/catch。

try
{
   file = new FileInputStream(fileName);
   x = (byte) file.read();
}catch(IOException i)
{
   i.printStackTrace();
   return -1;
}catch(FileNotFoundException f) //Not valid!
{
   f.printStackTrace();
   return -1;
}

throws/throw關鍵字:

如果一個方法沒有捕獲一個檢查性異常,那麼該方法必須使用throws 關鍵字來聲明。 throws關鍵字放在方法簽署的尾部。

也可以使用throw關鍵字拋出一個異常,無論它是新實例化的還是剛捕獲到的。

下面方法的宣告拋出一個RemoteException異常:

import java.io.*;
public class className
{
   public void deposit(double amount) throws RemoteException
   {
      // Method implementation
      throw new RemoteException();
   }
   //Remainder of class definition
}

一個方法可以宣告拋出多個例外,多個例外之間用逗號隔開。

例如,下面的方法宣告拋出RemoteException和InsufficientFundsException:

import java.io.*;
public class className
{
   public void withdraw(double amount) throws RemoteException,
                              InsufficientFundsException
   {
       // Method implementation
   }
   //Remainder of class definition
}

finally關鍵字

finally關鍵字用來建立在try程式碼區塊後面執行的代碼塊。

無論是否發生異常,finally程式碼區塊中的程式碼總是會被執行。

在finally程式碼區塊中,可以運行清理類型等收尾善後性質的語句。

finally程式碼區塊出現在catch程式碼區塊最後,語法如下:

 try{
    // 程序代码
 }catch(异常类型1 异常的变量名1){
    // 程序代码
 }catch(异常类型2 异常的变量名2){
    // 程序代码
 }finally{
    // 程序代码
 }

實例

 public class ExcepTest{

   public static void main(String args[]){
      int a[] = new int[2];
      try{
         System.out.println("Access element three :" + a[3]);
      }catch(ArrayIndexOutOfBoundsException e){
         System.out.println("Exception thrown  :" + e);
      }
      finally{
         a[0] = 6;
         System.out.println("First element value: " +a[0]);
         System.out.println("The finally statement is executed");
      }
   }
}

以上實例編譯執行結果如下:

Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
First element value: 6
The finally statement is executed

注意下面事項:

  •                 catch不能獨立於try存在。

  •                 在try/catch後面加入finally區塊並非強制性要求的。

  •                 try程式碼後不能既沒catch區塊也沒finally區塊。

  •                 try, catch, finally區塊之間不能新增任何程式碼。


宣告自訂例外

在Java中你可以自訂例外。編寫自己的異常類別時需要記住下面的幾點。

  •                 所有異常都必須是Throwable的子類別。

  •                 如果希望寫一個檢查性異常類,則需要繼承Exception類別。

  •                 如果你想寫一個運行時異常類,那麼你需要繼承RuntimeException 類別。

可以像下面這樣定義自己的例外類別:

class MyException extends Exception{
}

只繼承Exception 類別來建立的例外類別是檢查性異常類別。

下面的InsufficientFundsException類別是使用者定義的異常類,它繼承自Exception。

一個異常類別和其它任何類別一樣,包含有變數和方法。

實例

// 文件名InsufficientFundsException.java
import java.io.*;

public class InsufficientFundsException extends Exception
{
   private double amount;
   public InsufficientFundsException(double amount)
   {
      this.amount = amount;
   } 
   public double getAmount()
   {
      return amount;
   }
}

為了展示如何使用我們自訂的例外類,

在下面的CheckingAccount 類別中包含一個withdraw()方法拋出一個InsufficientFundsException例外。

// 文件名称 CheckingAccount.java
import java.io.*;

public class CheckingAccount
{
   private double balance;
   private int number;
   public CheckingAccount(int number)
   {
      this.number = number;
   }
   public void deposit(double amount)
   {
      balance += amount;
   }
   public void withdraw(double amount) throws
                              InsufficientFundsException
   {
      if(amount <= balance)
      {
         balance -= amount;
      }
      else
      {
         double needs = amount - balance;
         throw new InsufficientFundsException(needs);
      }
   }
   public double getBalance()
   {
      return balance;
   }
   public int getNumber()
   {
      return number;
   }
}

下面的BankDemo程式示範如何呼叫CheckingAccount類別的deposit() 和withdraw()方法。

//文件名称 BankDemo.java
public class BankDemo
{
   public static void main(String [] args)
   {
      CheckingAccount c = new CheckingAccount(101);
      System.out.println("Depositing 0...");
      c.deposit(500.00);
      try
      {
         System.out.println("\nWithdrawing 0...");
         c.withdraw(100.00);
         System.out.println("\nWithdrawing 0...");
         c.withdraw(600.00);
      }catch(InsufficientFundsException e)
      {
         System.out.println("Sorry, but you are short $"
                                  + e.getAmount());
         e.printStackTrace();
      }
    }
}

編譯上面三個文件,並執行程式BankDemo,得到結果如下所示:

Depositing 0...

Withdrawing 0...

Withdrawing 0...
Sorry, but you are short 0.0
InsufficientFundsException
        at CheckingAccount.withdraw(CheckingAccount.java:25)
        at BankDemo.main(BankDemo.java:13)

通用異常

在Java中定義了兩種類型的異常和錯誤。

  • JVM(Java虛擬機器#)」異常:由JVM拋出的例外或錯誤。例如:NullPointerException類,ArrayIndexOutOfBoundsException類,ClassCastException類別。

  • 程式級例外:由程式或API程式拋出的例外。例如IllegalArgumentException類,IllegalStateException類。

序號 ##方法及說明
public String getMessage() 傳回關於發生的異常的詳細資訊。這個訊息在Throwable 類別的建構子中初始化了。
public Throwable getCause() 傳回一個Throwable 物件代表異常原因。
public String toString()# 使用getMessage()的結果傳回類別的串級名字。
public void printStackTrace() 印出toString()結果和堆疊層次到System.err,即錯誤輸出流。
public StackTraceElement [] getStackTrace()# 傳回一個包含堆疊層次的陣列。下標為0的元素代表棧頂,最後一個元素代表方法呼叫堆疊的棧底。
public Throwable fillInStackTrace() 用目前的呼叫堆疊層次填入Throwable 物件棧層次,加入到棧層次任何先前資訊。