Java 語(yǔ)言要求 java 程序中(無(wú)論是誰(shuí)寫的代碼),所有拋出( throw )的異常都必須是從 Throwable 派生而來(lái)。 當(dāng)然,實(shí)際的 Java 編程中,由于 JDK 平臺(tái)已經(jīng)為我們?cè)O(shè)計(jì)好了非常豐富和完整的異常對(duì)象分類模型。因此, java 程序員一般是不需要再重新定義自己的異常對(duì)象。而且即便是需要擴(kuò)展自定義的異常對(duì)象,也往往會(huì)從 Exception 派生而來(lái)。所以,對(duì)于 java 程序員而言,它一般只需要在它的頂級(jí)函數(shù)中 catch(Exception ex) 就可以捕獲出所有的異常對(duì)象。 所有異常對(duì)象的根基類是 Throwable , Throwable 從 Object 直接繼承而來(lái)(這是 java 系統(tǒng)所強(qiáng)制要求的),并且它實(shí)現(xiàn)了 Serializable 接口(這為所有的異常對(duì)象都能夠輕松跨越 Java 組件系統(tǒng)做好了最充分的物質(zhì)準(zhǔn)備)。從 Throwable 直接派生出的異常類有 Exception 和 Error 。 Exception 是 java 程序員所最熟悉的,它一般代表了真正實(shí)際意義上的異常對(duì)象的根基類。也即是說(shuō), Exception 和從它派生而來(lái)的所有異常都是應(yīng)用程序能夠 catch 到的,并且可以進(jìn)行異常錯(cuò)誤恢復(fù)處理的異常類型。而 Error 則表示 Java 系統(tǒng)中出現(xiàn)了一個(gè)非常嚴(yán)重的異常錯(cuò)誤,并且這個(gè)錯(cuò)誤可能是應(yīng)用程序所不能恢復(fù)的,例如 LinkageError ,或 ThreadDeath 等。
首先還是看一個(gè)例子吧!代碼如下:
import java.io.*;
public class Trans
{
public static void main(String[] args)
{
try
{
BufferedReader rd=null;
Writer wr=null;
try
{
File srcFile = new File((args[0]));
File dstFile = new File((args[1]));
rd = new BufferedReader(new InputStreamReader(new FileInputStream(srcFile), args[2]));
wr = new OutputStreamWriter(new FileOutputStream(dstFile), args[3]);
// 注意下面這條語(yǔ)句,它有什么問(wèn)題嗎?
if (rd == null || wr == null) throw new Exception("error! test!");
while(true)
{
String sLine = rd.readLine();
if(sLine == null) break;
wr.write(sLine);
wr.write("\r\n");
}
}
finally
{
wr.flush();
wr.close();
rd.close();
}
}
catch(IOException ex)
{
ex.printStackTrace();
}
}
}
熟悉 java 語(yǔ)言的程序員朋友們,你們認(rèn)為上面的程序有什么問(wèn)題嗎?編譯能通過(guò)嗎?如果不能,那么原因又是為何呢?好了,有了自己的分析和預(yù)期之后,不妨親自動(dòng)手編譯一下上面的小程序,呵呵!結(jié)果確實(shí)如您所料?是的,的確是編譯時(shí)報(bào)錯(cuò)了,錯(cuò)誤信息如下:
E:\Trans.java:20: unreported exception java.lang.Exception; must be caught or declared to be thrown
if (rd == null || wr == null) throw new Exception("error! test!");
1 error
上面這種編譯錯(cuò)誤信息,相信 Java 程序員肯定見(jiàn)過(guò)(可能還是屢見(jiàn)不鮮!)!
相信老練一些的 Java 程序員一定非常清楚上述編譯出錯(cuò)的原因。那就是如錯(cuò)誤信息中(“ must be caught ”)描述的那樣, 在 Java 的異常處理模型中,要求所有被拋出的異常都必須要有對(duì)應(yīng)的“異常處理模塊” 。也即是說(shuō),如果你在程序中 throw 出一個(gè)異常,那么在你的程序中(函數(shù)中)就必須要 catch 這個(gè)異常(處理這個(gè)異常)。例如上面的例子中,你在第 20 行代碼處,拋出了一個(gè) Exception 類型的異常,但是在該函數(shù)中,卻沒(méi)有 catch 并處理掉此異常的地方。因此,這樣的程序即便是能夠編譯通過(guò),那么運(yùn)行時(shí)也是致命的(可能導(dǎo)致程序的崩潰),所以, Java 語(yǔ)言干脆在編譯時(shí)就盡可能地檢查(并卡住)這種本不應(yīng)該出現(xiàn)的錯(cuò)誤,這無(wú)疑對(duì)提高程序的可靠性大有幫助。
但是,在 Java 語(yǔ)言中,這就是必須的。 如果一個(gè)函數(shù)中,它運(yùn)行時(shí)可能會(huì)向上層調(diào)用者函數(shù)拋出一個(gè)異常,那么,它就必須在該函數(shù)的聲明中顯式的注明(采用 throws 關(guān)鍵字) 。還記得剛才那條編譯錯(cuò)誤信息嗎?“ must be caught or declared to be thrown ”,其中“ must be caught ”上面已經(jīng)解釋了,而后半部分呢?“ declared to be thrown ”是指何意呢?其實(shí)指的就是“必須顯式地聲明某個(gè)函數(shù)可能會(huì)向外部拋出一個(gè)異常”,也即是說(shuō),如果一個(gè)函數(shù)內(nèi)部,它可能拋出了一種類型的異常,但該函數(shù)內(nèi)部并不想(或不宜) catch 并處理這種類型的異常,此時(shí),它就必須使用 throws 關(guān)鍵字來(lái)聲明該函數(shù)可能會(huì)向外部拋出一個(gè)異常,以便于該函數(shù)的調(diào)用者知曉并能夠及時(shí)處理這種類型的異常。下面列出了這幾種情況的比較,代碼如下:
// 示例程序 1 ,這種寫法能夠編譯通過(guò)
package com.ginger.exception;
import java.io.*;
public class Trans {
public static void main(String[] args) {
try {
test();
} catch (Exception ex) {
ex.printStackTrace();
}
}
static void test() {
try {
throw new Exception("To show Exception Successed");
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
// 示例程序 2 ,這種寫法就不能夠編譯通過(guò)
import java.io.*;
public class Trans {
public static void main(String[] args) {
try {
test();
}
// 雖然這里能夠捕獲到 Exception 類型的異常
catch (Exception ex) {
ex.printStackTrace();
}
}
static void test() {
throw new Exception("test");
}
}
// 示例程序 3 ,這種寫法又能夠被編譯通過(guò)
import java.io.*;
public class Trans
{
public static void main(String[] args)
{
try
{
test();
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
// 由于函數(shù)聲明了可能拋出 Exception 類型的異常
static void test() throws Exception
{
throw new Exception("test");
}
}
// 示例程序 4 ,它又不能夠被編譯通過(guò)了
import java.io.*;
public class Trans
{
public static void main(String[] args)
{
try
{
// 雖然 test() 函數(shù)并沒(méi)有真正拋出一個(gè) Exception 類型的異常
// 但是由于它函數(shù)聲明時(shí),表示它可能拋出一個(gè) Exception 類型的異常
// 所以,這里仍然不能被編譯通過(guò)。
// 呵呵!體會(huì)到了 Java 異常處理模型的嚴(yán)謹(jǐn)吧!
test();
}
catch(IOException ex)
{
ex.printStackTrace();
}
}
static void test() throws Exception
{
}
}
不知上面幾個(gè)有聯(lián)系的示例是否能夠給大家?guī)?lái)“豁然開(kāi)朗”的感覺(jué),坦率的說(shuō), Java 提供的異常處理模型并不復(fù)雜,相信太多太多 Java 程序員有著比我更深刻的認(rèn)識(shí)。最后,補(bǔ)充一種例外情況,請(qǐng)看如下代碼:
import java.io.*;
public class Trans {
public static void main(String[] args) {
try {
test();
} catch (Exception ex) {
ex.printStackTrace();
}
}
static void test() throws Error {
throw new Error(" 故意拋出一個(gè) Error");
}
}
朋友們!上面的程序能被編譯通過(guò)嗎?注意,按照剛才上面所總結(jié)出的規(guī)律:在 Java 的異常處理模型中,要求所有被拋出的異常都必須要有對(duì)應(yīng)的 catch 塊!那么上面的程序肯定不能被編譯通過(guò),因?yàn)?Error 和 Exception 都是從 Throwable 直接派生而來(lái),而 test 函數(shù)聲明了它可能拋出 Error 類型的異常,但在 main 函數(shù)中卻并沒(méi)有 catch(Error) 或 catch(Throwable) 塊,所以它理當(dāng)是會(huì)編譯出錯(cuò)的!真的嗎?不妨試試!呵呵!結(jié)果并非我們之預(yù)料,而它恰恰是正確編譯通過(guò)了。為何? WHY ? WHY ?
其實(shí),原因很簡(jiǎn)單,那就是因?yàn)?Error 異常的特殊性。 Java 異常處理模型中規(guī)定: Error 和從它派生而來(lái)的所有異常,都表示系統(tǒng)中出現(xiàn)了一個(gè)非常嚴(yán)重的異常錯(cuò)誤,并且這個(gè)錯(cuò)誤可能是應(yīng)用程序所不能恢復(fù)的(其實(shí)這在前面的內(nèi)容中已提到過(guò))。因此,如果系統(tǒng)中真的出現(xiàn)了一個(gè) Error 類型的異常,那么則表明,系統(tǒng)已處于崩潰不可恢復(fù)的狀態(tài)中,此時(shí),作為編寫 Java 應(yīng)用程序的你,已經(jīng)是沒(méi)有必要(也沒(méi)能力)來(lái)處理此等異常錯(cuò)誤。所以, javac 編譯器就沒(méi)有必要來(lái)保證:“在編譯時(shí),所有的 Error 異常都有其對(duì)應(yīng)的錯(cuò)誤處理模塊”。當(dāng)然, Error 類型的異常一般都是由系統(tǒng)遇到致命的錯(cuò)誤時(shí)所拋出的,它最后也由 Java 虛擬機(jī)所處理。而作為 Java 程序員的你,可能永遠(yuǎn)也不會(huì)考慮拋出一個(gè) Error 類型的異常。因此 Error 是一個(gè)特例情況!
特別關(guān)注一下 RuntimeException
上面剛剛討論了一下 Error 類型的異常處理情況, Java 程序員一般無(wú)須關(guān)注它(處理這種異常)。另外,其實(shí)在 Exception 類型的異常對(duì)象中,也存在一種比較特別的“異常”類型,那就是 RuntimeException ,雖然它是直接從 Exception 派生而來(lái),但是 Java 編譯器( javac )對(duì) RuntimeException 卻是特殊待遇,而且是照顧有加。不信,看看下面的兩個(gè)示例吧!代碼如下:
// 示例程序 1
// 它不能編譯通過(guò),我們可以理解
import java.io.*;
public class Trans {
public static void main(String[] args) {
test();
}
static void test() {
// 注意這條語(yǔ)句
throw new Exception(" 故意拋出一個(gè) Exception");
}
}
// 示例程序 2
// 可它卻為什么能夠編譯通過(guò)呢?
import java.io.*;
public class Trans {
public static void main(String[] args) {
test();
}
static void test() {
// 注意這條語(yǔ)句
throw new RuntimeException(" 故意拋出一個(gè) RuntimeException");
}
}
對(duì)上面兩個(gè)相當(dāng)類似的程序, javac 編譯時(shí)卻遭遇了兩種截然不同的處理,按理說(shuō),第 2 個(gè)示例程序也應(yīng)該像第 1 個(gè)示例程序那樣,編譯時(shí)報(bào)錯(cuò)!但是 javac 編譯它時(shí),卻例外地讓它通過(guò)它,而且在運(yùn)行時(shí), java 虛擬機(jī)也捕獲到了這個(gè)異常,并且會(huì)在 console 打印出詳細(xì)的異常信息。運(yùn)行結(jié)果如下:
java.lang.RuntimeException: 故意拋出一個(gè) RuntimeException
at Trans.test(Trans.java:13)
at Trans.main(Trans.java:8)
Exception in thread "main"
為什么對(duì)于 RuntimeException 類型的異常(以及從它派生而出的異常類型), javac 和 java 虛擬機(jī)都特殊處理呢?要知道,這可是與“ Java 異常處理模型更嚴(yán)謹(jǐn)和更安全”的設(shè)計(jì)原則相抵觸的呀!究竟是為何呢?這簡(jiǎn)直讓人不法理解呀!
只不過(guò), Java 語(yǔ)言中, RuntimeException 被統(tǒng)一納入到了 Java 語(yǔ)言和 JDK 的規(guī)范之中。請(qǐng)看如下代碼,來(lái)驗(yàn)證一下我們的理解!
import java.io.*;
public class Trans
{
public static void main(String[] args)
{
test();
}
static void test()
{
int i = 4;
int j = 0;
// 運(yùn)行時(shí),這里將觸發(fā)了一個(gè) ArithmeticException
// ArithmeticException 從 RuntimeException 派生而來(lái)
System.out.println("i / j = " + i / j);
}
}
運(yùn)行結(jié)果如下:
java.lang.ArithmeticException: / by zero
at Trans.test(Trans.java:16)
at Trans.main(Trans.java:8)
Exception in thread "main"
又如下面的例子,也會(huì)產(chǎn)生一個(gè) RuntimeException ,代碼如下:
import java.io.*;
public class Trans
{
public static void main(String[] args)
{
test();
}
static void test()
{
String str = null;
// 運(yùn)行時(shí),這里將觸發(fā)了一個(gè) NullPointerException
// NullPointerException 從 RuntimeException 派生而來(lái)
str.compareTo("abc");
}
}
所以,針對(duì) RuntimeException 類型的異常, javac 是無(wú)法通過(guò)編譯時(shí)的靜態(tài)語(yǔ)法檢測(cè)來(lái)判斷到底哪些函數(shù)(或哪些區(qū)域的代碼)可能拋出這類異常(這完全取決于運(yùn)行時(shí)狀態(tài),或者說(shuō)運(yùn)行態(tài)所決定的),也正因?yàn)槿绱耍?Java 異常處理模型中的“ must be caught or declared to be thrown ”規(guī)則也不適用于 RuntimeException (所以才有前面所提到過(guò)的奇怪編譯現(xiàn)象,這也屬于特殊規(guī)則吧)。但是, Java 虛擬機(jī)卻需要有效地捕獲并處理此類異常。當(dāng)然, RuntimeException 也可以被程序員顯式地拋出,而且為了程序的可靠性,對(duì)一些可能出現(xiàn)“運(yùn)行時(shí)異常( RuntimeException )”的代碼區(qū)域,程序員最好能夠及時(shí)地處理這些意外的異常,也即通過(guò) catch(RuntimeExcetion) 或 catch(Exception) 來(lái)捕獲它們。如下面的示例程序,代碼如下:
import java.io.*;
public class Trans
{
public static void main(String[] args)
{
try
{
test();
}
// 在上層的調(diào)用函數(shù)中,最好捕獲所有的 Exception 異常!
catch(Exception e)
{
System.out.println("go here!");
e.printStackTrace();
}
}
// 這里最好顯式地聲明一下,表明該函數(shù)可能拋出 RuntimeException
static void test() throws RuntimeException
{
String str = null;
// 運(yùn)行時(shí),這里將觸發(fā)了一個(gè) NullPointerException
// NullPointerException 從 RuntimeException 派生而來(lái)
str.compareTo("abc");
}
}