深入探討Java中的異常與錯誤處理

codeceo發表於2015-03-15

Java中的異常處理機制已經比較成熟,我們的Java程式到處充滿了異常的可能,如果對這些異常不做預先的處理,那麼將來程式崩潰就無從除錯,很難找到異常所在的位置。本文將探討一下Java中異常與錯誤的處理方法,一起來看看。

異常與錯誤:

異常:

在Java中程式的錯誤主要是語法錯誤和語義錯誤,一個程式在編譯和執行時出現的錯誤我們統一稱之為異常,它是VM(虛擬機器)通知你的一種方式,通過這種方式,VM讓你知道,你(開發人員)已經犯了個錯誤,現在有一個機會來修改它。Java中使用異常類來表示異常,不同的異常類代表了不同的異常。但是在Java中所有的異常都有一個基類,叫做Exception。

錯誤:

它指的是一個合理的應用程式不能截獲的嚴重的問題。大多數都是反常的情況。錯誤是VM的一個故障(雖然它可以是任何系統級的服務)。所以,錯誤是很難處理的,一般的開發人員(當然不是你)是無法處理這些錯誤的,比如記憶體溢位。 和異常一樣,在Java中用錯誤類來表示錯誤,不同的錯誤類代表了不同的錯誤。 但是在Java中所有的錯誤都有一個基類,叫做Error。

綜上,我們可以知道異常和錯誤最本質的區別就是異常能被開發人員處理而錯誤時系統本來自帶的,一般無法處理也不需要我們程式設計師來處理。

1.一個異常是在一個程式執行過程中出現的一個事件,它中斷了正常指令的執行
2.錯誤,偏離了可接受的程式碼行為的一個動作或例項

異常的結構分類:

1、執行時異常(未檢查異常)
2、編譯時異常(已檢查異常)

執行異常即是RuntimeException;其餘的全部為編譯異常

在Java中異常Exception和錯誤Error有個共同的父類Throwable。

Error Exception

runtimeException幾個子類
1、 java.lang.ArrayIndexOutOfBoundsException
陣列索引越界異常。當對陣列的索引值為負數或大於等於陣列大小時丟擲。

2、java.lang.ArithmeticException
算術條件異常。譬如:整數除零等。

3、java.lang.NullPointerException
空指標異常。當應用試圖在要求使用物件的地方使用了null時,丟擲該異常。譬如:呼叫null物件的例項方法、訪問null物件的
屬性、計算null物件的長度、使用throw語句丟擲null等等

4、java.lang.ClassNotFoundException
找不到類異常。當應用試圖根據字串形式的類名構造類,而在遍歷CLASSPAH之後找不到對應名稱的class檔案時,丟擲
該異常。

對異常的處理:

try{}catch{}
try{}catch{}finally{}無論有無異常finally程式碼塊都會被執行
try{}finally{}也是可以組合使用的但是catch{}finally{}不可以

注意:在繼承關係中,子類覆蓋父類的方法,丟擲異常的範圍不能比父類更寬泛

異常的使用

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

例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:
異常出現前
陣列越界錯誤
常出現後
*/

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中還是有好多的東西需要深挖的。

相關文章