### 1. 異常分類
在Java程序設計語言中,異常對象都是派生于Throwable類的一個實例。其是如果Java中的異常類不能滿足需求,用戶可以創建自己的異常類。
下圖是Java異常層次結構的一個簡化示意圖。

從圖上可以看出,所有的異常都是繼承于Throwable類,但是在下一層立即分解為兩個分支:Error和Exception。
(1)Error
Error描述了Java運行時系統的內部錯誤和資源耗盡錯誤。應用程序不應該拋出這種類型的錯誤,如果出現了這樣的內部錯誤,除了通告用戶,并盡力使程序安全的終止之外,再也無能為力了。這種情況很少見。大多數錯誤與代碼編寫者執行的操作無關,而表示代碼運行時?JVM出現的問題。例如,Java虛擬機運行錯誤(Virtual MachineError),當 JVM 不再有繼續執行操作所需的內存資源時,將出現 OutOfMemoryError。這些異常發生時,Java虛擬機(JVM)一般會選擇線程終止。

這些錯誤表示故障發生于虛擬機自身、或者發生在虛擬機試圖執行應用時,并且這些錯誤是不可查的,因為它們在應用程序的控制和處理能力之 外,而且絕大多數是程序運行時不允許出現的狀況。對于設計合理的應用程序來說,即使確實發生了錯誤,本質上也不應該試圖去處理它所引起的異常狀況。
(2)Exception
程序設計者應該關注的是Exception,這一層次異常又分為兩個分支:IOException和RuntimeException。劃分這兩個分支的規則是:由程序錯誤導致的異常屬于RuntimeException;而程序本身沒有問題,但是由于IO錯誤這類問題導致的異常屬于IOException。

派生于RuntimeException的異常包括:
- 異常的運算條件,如一個整數除以0時
- 錯誤的類型轉換
- 數組訪問越界
- 訪問空指針
派生于IOException的異常包括:
- 試圖在文件尾部后面讀取數據
- 試圖打開一個不存在的文件
“如果出現RuntimeException異常則表明一定是你的問題”,這是一條相當有道理的規則。
<table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1042px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187)"><br/>? ? ?注意:<br/>? ? ? ? ? ? ?異常和錯誤的區別:異常能被程序本身可以處理,錯誤是無法處理。</td></tr></tbody></table>
(3)Checked Exception 與 UnChecked Exception
Java語言規范將派生于Error類或者RuntimeException類的所有異常稱為未檢查異常(UnChecked 異常),所有其他的異常(包括IOException)稱為已檢查異常(Checked 異常)。編譯器將核查是否為所有的Checked 異常提供了異常處理器。
### 2.聲明已檢查異常
如果遇到了無法處理的情況,那么Java的方法可以拋出一個異常。這個道理很簡單:一個方法不僅需要告訴編譯器將要返回什么值,還要告訴編譯器有可能發生什么錯誤。
例如:一段讀取文件的代碼知道優肯風讀取的文件不存在,或者內容為空,因此,試圖處理文件信息的代碼就需要通知編譯器可能會拋出IOException異常。
方法應該聲明所有可能拋出的已檢查異常,這樣可以反映出這個方法可能拋出哪類已檢查異常。
例如:下面是標準類庫中提供的FileInputStream類的一個構造方法的聲明異常情況:
~~~
public FileInputStream(String name) throws FileNotFoundException
~~~
這個異常聲明表示這個構造方法根據給定的字符串name正確情況下產生一個FileInputStream對象,但是也有可能拋出一個FileNotFoundException異常。如果拋出異常,方法不會初始化一個FileInputStream對象,而是拋出一個FileNotFoundException對象。拋出異常之后,運行時系統開始搜索異常處理器,以便知道如何處理?FileNotFoundException對象。
不是所有可能拋出的異常都必須進行聲明,以下4種情況時記得拋出異常:
- 調用一個拋出已檢查異常的方法,如FileInputStream構造方法
- 程序運行過程中發現錯誤,并且利用throw語句拋出一個已檢查異常
- 程序出現錯誤,例如,a[-1]=0會拋出一個下標越界的未檢查異常
- Java虛擬機和運行庫出現的內部錯誤
對于前兩種情況必須進行聲明。
不需要聲明Java的內部錯誤,即從Error繼承的錯誤,任何程序代碼都具有拋出那些異常的潛能,但是它們在我們的控制范圍之外。同樣也不應該聲明從RuntimeException繼承的那些未檢查異常。
~~~
void Read(int index) throws ArrayIndexOutOfBoundsException // bad style
{
...
}
~~~
這些運行時錯誤完全在我們的控制范圍之內,如果特別關注數組下標引發的錯誤,就會將更多的時間花費在修正程序中的錯誤上,而不是說明這些錯誤發生的可能性上。
總結:
<table cellspacing="0" cellpadding="0" style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1042px"><tbody><tr><td style="border-collapse:collapse; border:1px solid rgb(187,187,187); width:1041px"><span style="color:#ff0000"><br/></span><ol><li><span style="color:#ff0000; font-size:10.5pt; line-height:1.5">一個方法必須聲明所有可能拋出的已檢查異常,而未檢查異常要么不可控制(Error),要么就應該避免發生(RuntimeException)。</span><br/></li><li><span style="color:rgb(255,0,0); font-size:10.5pt; line-height:1.5">?如果一個方法沒有聲明所有可能發生的已檢查異常,編譯器就會給出一個錯誤信息。</span><br/></li><li><span style="color:rgb(255,0,0); font-size:10.5pt; line-height:1.5">如果類中的一個方法聲明會拋出一個異常,而這個異常是某個特定類的實例時,則這個方法就有可能拋出一個這個類,或者這個類一個子類的異常。</span></li></ol></td></tr></tbody></table>
### 3.如何拋出異常
假設有一個方法用來讀取文件內容,給定的文本長度為1024,但是讀到700個字符之后文件就結束了,我們認定這不是一種正常的情況,希望拋出異常。
首先決定應該拋出什么類型的異常(EOFException),知道之后拋出異常的語句如下:
~~~
// 第一種方法
throw new EOFException();
// 第二種方法
EOFException e = new EOFException();
throw e;
~~~
EOFException類還有一個含有一個字符串參數的構造方法,可以更加細致描述異常出現的狀況:
~~~
String gripe = "未到指定長度,文件讀取結束";
throw new EOFException(gripe);
~~~
對于一個已經存在的異常類,拋出異常過程:
- 找到一個合適的異常類
- 創建這個異常類的一個對象
- 將對象拋出
### 4.創建異常類
在程序中,可能會遇到任何標準程序類都沒有能夠充分描述清楚的問題,這種情況下,我們需要創建我們自己的異常類。我們需要做的就是定義一個派生類于Exception,或者派生于Exception子類的類。習慣上,定義的類應該包含兩個構造器,一個是默認的構造器,一個是帶有詳細描述信息的構造器。
~~~
public class FileFormatException extends Exception{
/**
*
*/
private static final long serialVersionUID = 1L;
// 默認構造器
public FileFormatException(){
}
// 帶有詳細描述信息的構造器
public FileFormatException(String gripe){
super(gripe);
}
}
~~~
現在我們可以拋出我們自己定義的異常類型了。
~~~
throw new FileFormatException;
~~~
### 5.捕獲異常
如果某個異常發生的時候沒有任何地方進行捕獲,那程序就會終止,并在控制臺上打印出異常信息,其中包括異常的類型和堆棧的內容。
~~~
package com.qunar.test;
public class ExceptionTest {
public static void main(String[] args) {
int a = 10;
int b = 0;
System.out.printf("%d / %d = %d",a,b,a/b);
System.out.println("測試結束...");
}
}
~~~
控制臺信息:
Exception?in?thread?"main"?java.lang.ArithmeticException:?/?by?zero
????at?com.qunar.test.ExceptionTest.main(ExceptionTest.java:8)
從異常信息可以看出程序并沒有運行完全,沒有輸出“測試結束...”,程序就終止,并且在控制臺打印出異常信息。
要想捕獲異常,必須使用try/catch語句塊。
~~~
try{
code
more code
more code
}
catch (Exception e) {
handle for this type
}
~~~
(1)如果在try語句塊中任何代碼拋出一個在catch子句中說明的異常類,那么:
- 程序將跳過try語句塊的剩余代碼
- 程序將執行catch子句的處理器代碼
(2)如果在try語句塊中代碼沒有拋出異常,那么程序將跳過catch子句。
(3)如果方法中的任何代碼拋出了一個在catch子句沒有聲明的異常類型,那么這個方法就會立刻退出。
~~~
package com.qunar.test;
public class ExceptionTest {
public static void main(String[] args) {
int a = 10;
int b = 0;
try{
System.out.printf("%d / %d = %d",a,b,a/b);
}
catch (ArithmeticException e) {
System.out.println("a / b b 不能等于0");
}
System.out.println("測試結束...");
}
}
~~~
運行結果:
a?/?b??b?不能等于0
測試結束...??
看一個例子:(讀取文本程序代碼)
~~~
public void read(String name){
try{
InputStream inputStream = new FileInputStream(name);
int b;
while((b = inputStream.read()) != -1){
// ...
}//while
}
catch (IOException e) {
e.printStackTrace();
}
}
~~~
對于一個普通的程序來說,這樣的處理異常基本上合乎情理,但是,通常最好的情況是什么也不做,而是將異常傳遞給調用者。如果read方法出現了錯誤,就讓read方法的調用者去處理,如果采用這種處理方式,就必須聲明這個方法可能會拋出一個IOException。
~~~
public void read(String name) throws IOException{
InputStream inputStream = new FileInputStream(name);
int b;
while((b = inputStream.read()) != -1){
// ...
}//while
}
~~~
如果調用了一個拋出已檢查異常的方法,就必須對它進行處理或者將它繼續進行傳遞。
出現了兩種處理方式,那到底哪種方式更好呢?
通常,應該捕獲那些知道如何處理的異常,而將那些不知道怎么處理的異常繼續進行傳遞。如果想傳遞一個異常,就必須在方法添加一個throws說明符。仔細閱讀Java API文檔,以便知道每個方法可能會拋出哪種異常,然后再決定是自己處理,還是加到throws列表中。
### 6.捕獲多個異常
在一個try語句塊中可以捕獲多個異常類型,并對不同類型的異常做出不同的處理。
~~~
try{
}
catch (FileNotFoundException e) {
// emergency action for missing files
}
catch (UnknownException e) {
// emergency action for unknown hosts
}
catch (IOException e) {
// emergency action for all other I/O problems
}
~~~
### 7.再次拋出異常與異常鏈
在catch子句中可以拋出一個異常,這樣做的目的是改變異常的類型。如果開發了一個供其他程序員使用的子系統,那么用于表示系統故障的異常類型會有多種解釋。ServletException就是這樣一個異常類型的例子。執行Servlet的代碼可能不想知道發生的錯誤的細節原因,但希望知道servlet是否有問題。
下面給出了捕獲異常并將它再次拋出的基本方法:
~~~
try{
access the database
}
catch(SQLException e){// 發生錯誤的細節原因
throw new ServletException("database error:"+e.getMessage());// 只希望知道servlet是否有問題
}
~~~
再給出一個更好的方法:包裝技術
~~~
try{
access the databse
}
catch(SQLException e){
Throwable se = new ServletException("database error");
// 設置初始異常
se.initCause(e);
throw se;
}
~~~
這個方法更好的地方在于當捕獲到異常時可以使用下面語句得到原始異常(不會丟失原始異常的細節):
~~~
Throwable e = se.getCause();
~~~
這種方法還可以解決一下問題:
如果一個方法中發生了一個已檢查異常,而不允許拋出它,那么包裝技術就十分有用,我們可以捕獲這個已檢查異常,并把它包裝成一個運行時異常。
### 8.finally子句
當代碼拋出一個異常時,就會終止方法中剩余代碼的處理,并退出這個方法的執行。如果方法獲得了一些本地資源,并且只有這個方法知道,同時這些資源再退出之前必須回收。這就需要finally子句來解決。不管是否有異常,finally子句的代碼都被執行。
舉個例子,當發生異常時,恰當的關閉所有數據庫的鏈接是非常重要的,這種情況就可以使用finally。
~~~
try{
}
catch (Exception e) {
}
finally{
}
~~~
(1)如果代碼沒有拋出異常。首先執行try語句塊中的全部代碼,然后執行finally子句中的代碼。
(2)如果代碼拋出異常,并且在catch子句可以捕獲到。首先執行try語句塊匯總的所有代碼,直到發生異常為止,此時跳過try語句塊中剩余代碼,去執行與該異常匹配的catch子句中的代碼,最后執行finally子句中的代碼。
(3)如果代碼拋出異常,但這個異常不是由catch子句捕獲的。首先執行try語句塊匯總的所有代碼,直到發生異常為止,此時跳過try語句塊中剩余代碼,然后執行finally子句中的代碼,并將異常拋給這個方法的調用者。
建議獨立使用try/catch和try/finally語句塊。這樣可以提高代碼的清晰度。例如:
~~~
package com.qunar.test;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ExceptionTest {
public static void main(String[] args) {
String name = "";
InputStream in = null;
// 確保報告出現的錯誤
try{
in = new FileInputStream(name);
// 確保關閉輸入流
try{
// code that might throw exceptions
}
finally{
in.close();
}
}
catch (IOException e) {
// show error message
}
}
}
~~~
內層的try語句塊只有一個職責,就是確保關閉輸入流。外層的try語句塊也只有一個職責,就是確保報告出現的錯誤。這種設計不僅清楚,而且還具有一個功能,就是將會報告finally子句中出現的錯誤。
當finally子句包含return語句時,會出現意想不到的結果。假設利用return語句從try語句塊中退出。在方法返回前,finally子句的內容將被執行。如果finally子句中也有一個return語句,這個返回值將會覆蓋原始的返回值。
~~~
package com.qunar.test;
public class ExceptionTest {
public static int function(int n){
try{
int r = n * n;
return r;
}
finally{
if(n == 2){
return 0;
}//if
}//finally
}
public static void main(String[] args) {
System.out.println(function(2));
}
}
~~~
如果調用function(2),那么try語句塊中計算結果為4,并執行return語句。但是在方法真正返回前,還要執行finally子句,將使得方法返回0,覆蓋了原始的返回值4。
有時候,finally也會有麻煩。
~~~
InputStream in = ...;
try{
// code that might throw exceptions
}
finally{
in.close();
}
~~~
假設在try語句塊拋出了一個非IOException的異常,這個異常只有方法的調用者才能處理。執行finally子句,并調用in.close()方法,而close方法本身也可能拋出IOException異常。當這種情況出現時,原始的異常將會丟失,轉而拋出clsoe方法的異常。
- 前言
- [Hibernate開發之路](1)Hibernate配置
- [Hibernate開發之路](2)Hibernate問題
- [Hibernate開發之路](3)基礎配置
- [Hibernate開發之路](4)ID生成策略
- [Hibernate開發之路](5)聯合主鍵
- [設計模式實踐之路](1)單例模式
- [Java]UDP通信的簡單例子
- [Java]套接字地址InetAddress講解
- [Java開發之路](1)final關鍵字
- [Java開發之路](2)Java字符串
- [Java開發之路](3)Java常用類
- [Java開發之路](4)String、StringBuffer與StringBuilder詳解
- [Java開發之路](5)異常詳解
- [Java開發之路](6)File類的使用
- [Java開發之路](7)RandomAccessFile類詳解
- [Java開發之路](8)輸入流和輸出流
- [Java開發之路](9)對象序列化與反序列化
- [Java開發之路](10)DOM解析XML文檔
- [Java開發之路](11)SAX解析XML文檔
- [Java開發之路](12)JDOM和DOM4J解析XML文檔
- [Java開發之路](14)反射機制
- [Java開發之路](15)注解
- [Java開發之路](16)學習log4j日志
- [Java開發之路](18)關于Class.getResource和ClassLoader.getResource的路徑問題
- [Java開發之路](19)Long緩存問題
- [Java開發之路](20)try-with-resource 異常聲明
- [Java開發之路](21)Comparator與Comparable
- [Java]Java工程師成神之路
- [細說Java](1)圖說字符串的不變性
- [細說Java](2)Java中字符串為什么是不可變的
- [細說Java](3)創建字符串是使用&quot; &quot;還是構造函數?