異常(或異常事件)是在執行程式期間出現的問題。 當發生異常時,程式的正常流程被中斷並且程式/應用程式異常終止,這是對於用戶來說是非常不友好的。因此,要合理地處理這些異常。
發生異常有許多不同的原因,以下是發生異常的一些情況。
- 用戶輸入了無效數據。
- 找不到需要打開的檔。
- 在通信過程中丟失了網路連接,或者JVM記憶體不足。
有一些異常是由用戶錯誤引起的,也有一些異常是由程式員錯誤引起的,或者是由以某種物理資源引起的。
基於這三類異常,您需要瞭解它們以瞭解在Java中異常處理工作原理。
- 已檢查異常 - 已檢查異常是編譯器在編譯時檢查(通知)的異常,這些異常也稱為編譯時異常。這些異常不能簡單地忽略,程式員應該編寫代碼來處理這些異常。
 例如,如果在程式中使用FileReader類從檔中讀取數據,如果其構造函數中指定的檔不存在,則會發生FileNotFoundException異常,並且編譯器會提示程式員處理異常。示例代碼 -
import java.io.File;
import java.io.FileReader;
public class FilenotFound_Demo {
   public static void main(String args[]) {
      File file = new File("E://file.txt");
      FileReader fr = new FileReader(file);
   }
}
如果編譯上述程式,則會出現以下異常。
C:\>javac FilenotFound_Demo.java
FilenotFound_Demo.java:8: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
      FileReader fr = new FileReader(file);
                      ^
1 error
注 - 由於
FileReader類的read()和close()方法拋出IOException,可以看到到編譯器通知要求處理IOException以及FileNotFoundException這兩個異常。
- 未檢查異常 - 未檢查的異常是在執行時發生的異常。這些也稱為運行時異常。 這些包括編程錯誤,例如邏輯錯誤或API的不當使用,編譯時忽略運行時異常。 
 例如,如果在程式中聲明了一個大小為- 5的數組,但是卻要訪問數組的第- 6個元素,則會發生- ArrayIndexOutOfBoundsExceptionexception異常。- public class Unchecked_Demo { public static void main(String args[]) { int num[] = {1, 2, 3, 4}; System.out.println(num[5]);// 訪問第6個元素 } }- 如果編譯並執行上述程式,則會出現以下異常。 - Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5 at Exceptions.Unchecked_Demo.main(Unchecked_Demo.java:8)
- 錯誤 - 這個嚴格來說不是異常,它是超出用戶或程式員控制的問題。 代碼中通常會忽略錯誤,因為很少對錯誤做任何事情。 例如,如果發生堆疊溢出,則會出現錯誤。 它們在編譯時也被忽略。 
1. 異常層次結構
所有異常類都是java.lang.Exception類的子類型。 Exception類是Throwable類的子類。 除了Exception類之外,還有另一個名稱為Error的子類,它派生自Throwable類。
錯誤是在嚴重故障的情況下發生的異常情況,Java程式不處理這些情況。 生成錯誤以指示運行時環境生成的錯誤。例如:JVM記憶體不足。 通常,程式無法從錯誤中恢復。
Exception類有兩個主要的子類:IOException類和RuntimeException類。

以下是最常見的已檢查和未檢查的Java內置異常類列表。
2. 異常方法
以下是Throwable類中可用的方法列表。
| 編號 | 方法 | 異常 | 
|---|---|---|
| 1 | public String getMessage() | 返回有關已發生的異常的詳細消息,此消息在 Throwable構造函數中初始化。 | 
| 2 | public Throwable getCause() | 返回由 Throwable對象表示的異常的原因。 | 
| 3 | public String toString() | 返回與 getMessage()結果連接的類名稱。 | 
| 4 | public void printStackTrace() | 將 toString()的結果與堆疊跟蹤一起列印到System.err(錯誤輸出流)。 | 
| 5 | public StackTraceElement [] getStackTrace() | 返回包含堆疊跟蹤上每個元素的數組。 索引 0處的元素表示調用堆疊的頂部,而數組中的最後一個元素表示調用堆疊底部的方法。 | 
| 6 | public Throwable fillInStackTrace() | 使用當前堆疊跟蹤填充此 Throwable對象的堆疊跟蹤,添加堆疊跟蹤中的任何先前資訊。 | 
3. 捕捉異常
在方法中可使用try和catch關鍵字的組合捕獲異常。try/catch塊放在可能生成異常的代碼周圍。try/catch塊中的代碼稱為受保護代碼,使用try/catch的語法如下所示 - 
語法
try {
   // Protected code
} catch (ExceptionName e1) {
   // Catch block
}
將容易出現異常的代碼放在try塊中。 發生異常時,異常由與其關聯的catch塊處理。 每個try塊都應該緊跟一個catch塊或者一個塊finally。
catch語句涉及聲明嘗試捕獲的異常類型。 如果受保護代碼中發生異常,則會檢查try之後的catch塊(或多個塊)。如果發生的異常類型列在catch塊中,則異常將傳遞給catch塊,就像將參數傳遞給方法參數一樣。
示例
以下是使用2個元素聲明的數組,然後嘗試訪問引發異常的數組的第3個元素。
// 檔 : 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
4. 多個try塊
try塊後面可以跟多個catch塊,多個catch塊的語法如下所示 - 
語法
try {
   // Protected code
} catch (ExceptionType1 e1) {
   // Catch block
} catch (ExceptionType2 e2) {
   // Catch block
} catch (ExceptionType3 e3) {
   // Catch block
}
上面的語句中放置了三個catch塊,但只需一次嘗試即可獲得任意數量的塊。 如果受保護代碼中發生異常,則會將異常拋出到列表中的第一個catch塊。 如果拋出的異常的數據類型與ExceptionType1匹配,則會在那裏捕獲它。 如果不是,則異常傳遞給第二個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;
}
捕獲多種類型的例外
從Java 7開始,可以使用單個catch塊處理多個異常,此功能簡化了代碼。 下麵是應用示例 - 
catch (IOException|FileNotFoundException ex) {
   logger.log(ex);
   throw ex;
5. throws/throw關鍵字
如果方法不處理已檢查的異常,則該方法必須使用throws關鍵字聲明它。 throws關鍵字應放置在方法簽名的末尾。
可以使用throw關鍵字拋出異常,可以是新實例化的異常,也可以是剛捕獲的異常。
throws和throw關鍵字之間的區別是,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
}
6. finally塊
finally塊在try塊或catch塊之後。無論受保護的代碼塊是否發生異常,最終都會執行finally塊中的代碼。
使用finally塊運行要執行的任何清理類型語句,無論受保護代碼中發生什麼。
finally塊放置在catch塊的末尾,它的語法語法如下 - 
try {
   // Protected code
} catch (ExceptionType1 e1) {
   // Catch block
} catch (ExceptionType2 e2) {
   // Catch block
} catch (ExceptionType3 e3) {
   // Catch block
}finally {
   // The finally block always executes.
}
示例
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
使用finally時,需要注意以下規則 - 
- 沒有try語句就不能存在catch子句。
- 只要存在try/catch塊,finally子句就不是必須的。
- 如果沒有catch子句或finally子句,則try塊不能出現。
- try,- catch,- finally塊之間不能出現任何代碼。
7. try-with-resources
通常,當使用流,連接等任何資源時,要使用finally塊顯式關閉它們。 在下面的程式中使用FileReader從檔中讀取數據,然後使用finally塊關閉它。
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class ReadData_Demo {
   public static void main(String args[]) {
      FileReader fr = null;
      try {
         File file = new File("file.txt");
         fr = new FileReader(file); char [] a = new char[50];
         fr.read(a);   // reads the content to the array
         for(char c : a)
         System.out.print(c);   // prints the characters one by one
      } catch (IOException e) {
         e.printStackTrace();
      }finally {
         try {
            fr.close();
         } catch (IOException ex) {
            ex.printStackTrace();
         }
      }
   }
}
try-with-resources,也稱為自動資源管理,是Java 7中引入的一種新的異常處理機制,它自動關閉try/catch塊中使用的資源。
要使用此語句,只需在括弧內聲明所需的資源,創建的資源將在塊結束時自動關閉。 以下是try-with-resources語句的語法。
語法
try(FileReader fr = new FileReader("file path")) {
   // use the resource
   } catch () {
      // body of catch
   }
}
以下是使用try-with-resources語句讀取檔中數據的程式。
import java.io.FileReader;
import java.io.IOException;
public class Try_withDemo {
   public static void main(String args[]) {
      try(FileReader fr = new FileReader("E://file.txt")) {
         char [] a = new char[50];
         fr.read(a);   // reads the contentto the array
         for(char c : a)
         System.out.print(c);   // prints the characters one by one
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}
在使用try-with-resources語句時,請牢記以下幾點。
- 要使用帶有try-with-resources語句的類,它應該實現AutoCloseable介面,並且它的close()方法在運行時自動調用。
- 可以在try-with-resources語句中聲明多個類。
- 當在try-with-resources語句的try塊中聲明多個類時,這些類將以相反的順序關閉。
- 除括弧內的資源聲明外,其他都與try塊的普通try/catch塊相同。
- try中聲明的資源在- try-block開始之前實例化。
- 在try塊聲明的資源被隱式聲明為final。
8. 用戶定義的異常
可以在Java中創建自己的異常。 在編寫自己的異常類時,請注意以下幾點 -
- 所有異常必須是Throwable的子類。
- 如果要編寫由處理或聲明規則自動強制執行的已檢查異常,則需要擴展Exception類。
- 如果要編寫運行時異常,則需要擴展RuntimeException類。
可以定義自己的Exception類,如下所示 - 
class MyException extends Exception {
}
只需要擴展預定義的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 $500...");
      c.deposit(500.00);
      try {
         System.out.println("\nWithdrawing $100...");
         c.withdraw(100.00);
         System.out.println("\nWithdrawing $600...");
         c.withdraw(600.00);
      } catch (InsufficientFundsException e) {
         System.out.println("Sorry, but you are short $" + e.getAmount());
         e.printStackTrace();
      }
   }
}
執行上面示例代碼,得到以下結果 -
Depositing $500...
Withdrawing $100...
Withdrawing $600...
Sorry, but you are short $200.0
InsufficientFundsException
         at CheckingAccount.withdraw(CheckingAccount.java:25)
         at BankDemo.main(BankDemo.java:13)
常見異常
在Java中,可以定義兩個分類:異常和錯誤。
- JVM異常 - 這些是JVM獨佔或邏輯拋出的異常/錯誤。 示例:NullPointerException,ArrayIndexOutOfBoundsException,ClassCastException。
- 程式化異常 - 應用程式或API程式員明確拋出這些異常。 示例:IllegalArgumentException,IllegalStateException。
