java異常——RuntimeException和User Define Exception

滄海一滴發表於2014-05-30

1.RuntimeException

public class RuntimeException {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String str="123";
        int temp=Integer.parseInt(str);
        System.out.println(temp*temp);
    }
}

檢視parseInt方法的原始碼如下:

 public static int parseInt(String s) throws NumberFormatException {
    return parseInt(s,10);
    }

 

我們發現這個方法中丟擲了NumberFormatException異常,但是在上面的程式碼中我們沒有找到try...catch來處理,這是為什麼呢。按照我們異常處理的知識,如果一個方法通過throws丟擲了異常,那麼可以在丟擲異常的方法中不使用try...catch,但是在呼叫這個方法的地方必須有try...catch來處理。

下面來觀察NumberFormatException類的繼承關係:

從上圖我們可以發現NumberFormatException是RuntimeException的子類,那麼這就需要我們清楚Exception和RuntimeException的概念:

  1. Exception:在程式中必須使用try...catch進行處理。
  2. RuntimeException:可以不使用try...catch進行處理,但是如果有異常產生,則異常將由JVM進行處理。

對於RuntimeException的子類最好也使用異常處理機制。雖然RuntimeException的異常可以不使用try...catch進行處理,但是如果一旦發生異常,則肯定會導致程式中斷執行,所以,為了保證程式再出錯後依然可以執行,在開發程式碼時最好使用try...catch的異常處理機制進行處理。

 

 

Throwable是所有異常的基類,程式中一般不會直接丟擲Throwable物件,Exception和Error是Throwable的子類,Exception下面又有RuntimeException和一般的Exception兩類。可以把JAVA異常分為三類:
        第一類是Error,Error表示程式在執行期間出現了十分嚴重、不可恢復的錯誤,在這種情況下應用程式只能中止執行,例如JAVA 虛擬機器出現錯誤。Error是一種unchecked Exception,編譯器不會檢查Error是否被處理,在程式中不用捕獲Error型別的異常;一般情況下,在程式中也不應該丟擲Error型別的異常。
        第二類是RuntimeException, RuntimeException 是一種unchecked Exception,即表示編譯器不會檢查程式是否對RuntimeException作了處理,在程式中不必捕獲RuntimException型別的異常,也不必在方法體宣告丟擲RuntimeException類。RuntimeException發生的時候,表示程式中出現了程式設計錯誤,所以應該找出錯誤修改程式,而不是去捕獲RuntimeException。
        第三類是一般的checked Exception,這也是在程式設計中使用最多的Exception,所有繼承自Exception並且不是RuntimeException的異常都是checked Exception,如圖1中的IOException和ClassNotFoundException。JAVA 語言規定必須對checked Exception作處理,編譯器會對此作檢查,要麼在方法體中宣告丟擲checked Exception,要麼使用catch語句捕獲checked Exception進行處理,不然不能通過編譯。checked Exception用於以下的語義環境:

(1) 該異常發生後是可以被恢復的,如一個Internet連線發生異常被中止後,可以重新連線再進行後續操作。
(2) 程式依賴於不可靠的外部條件,該依賴條件可能出錯,如系統IO。
(3) 該異常發生後並不會導致程式處理錯誤,進行一些處理後可以繼續後續操作。

4 JAVA異常處理中的注意事項
合理使用JAVA異常機制可以使程式健壯而清晰,但不幸的是,JAVA異常處理機制常常被錯誤的使用,下面就是一些關於Exception的注意事項:

1. 不要忽略checked Exception
請看下面的程式碼:
try
{
  method1();  //method1丟擲ExceptionA
}
catch(ExceptionA e)
{
    e.printStackTrace();
}
上面的程式碼似乎沒有什麼問題,捕獲異常後將異常列印,然後繼續執行。事實上在catch塊中對發生的異常情況並沒有作任何處理(列印異常不能是算是處理異常,因為在程式交付執行後除錯資訊就沒有什麼用處了)。這樣程式雖然能夠繼續執行,但是由於這裡的操作已經發生異常,將會導致以後的操作並不能按照預期的情況發展下去,可能導致兩個結果:
一是由於這裡的異常導致在程式中別的地方丟擲一個異常,這種情況會使程式設計師在除錯時感到迷惑,因為新的異常丟擲的地方並不是程式真正發生問題的地方,也不是發生問題的真正原因;
另外一個是程式繼續執行,並得出一個錯誤的輸出結果,這種問題更加難以捕捉,因為很可能把它當成一個正確的輸出。
那麼應該如何處理呢,這裡有四個選擇:

(1) 處理異常,進行修復以讓程式繼續執行。
(2) 重新丟擲異常,在對異常進行分析後發現這裡不能處理它,那麼重新丟擲異常,讓呼叫者處理。
(3) 將異常轉換為使用者可以理解的自定義異常再丟擲,這時應該注意不要丟失原始異常資訊(見5)。
(4) 不要捕獲異常。

因此,當捕獲一個unchecked Exception的時候,必須對異常進行處理;如果認為不必要在這裡作處理,就不要捕獲該異常,在方法體中宣告方法丟擲異常,由上層呼叫者來處理該異常。

2. 不要一次捕獲所有的異常
請看下面的程式碼:
try
{
  method1();  //method1丟擲ExceptionA
    method2();  //method1丟擲ExceptionB
    method3();  //method1丟擲ExceptionC
}
catch(Exception e)
{
    ……
}
這是一個很誘人的方案,程式碼中使用一個catch子句捕獲了所有異常,看上去完美而且簡潔,事實上很多程式碼也是這樣寫的。但這裡有兩個潛在的缺陷,一是針對try塊中丟擲的每種Exception,很可能需要不同的處理和恢復措施,而由於這裡只有一個catch塊,分別處理就不能實現。二是try塊中還可能丟擲RuntimeException,程式碼中捕獲了所有可能丟擲的RuntimeException而沒有作任何處理,掩蓋了程式設計的錯誤,會導致程式難以除錯。
下面是改正後的正確程式碼:
try
{
  method1();  //method1丟擲ExceptionA
    method2();  //method1丟擲ExceptionB
    method3();  //method1丟擲ExceptionC
}
catch(ExceptionA e)
{
    ……
}
catch(ExceptionB e)
{
    ……
}
catch(ExceptionC e)
{
    ……
}


3. 使用finally塊釋放資源
    finally關鍵字保證無論程式使用任何方式離開try塊,finally中的語句都會被執行。在以下三種情況下會進入finally塊:
(1) try塊中的程式碼正常執行完畢。
(2) 在try塊中丟擲異常。
(3) 在try塊中執行return、break、continue。
因此,當你需要一個地方來執行在任何情況下都必須執行的程式碼時,就可以將這些
程式碼放入finally塊中。當你的程式中使用了外界資源,如資料庫連線,檔案等,必須將釋放這些資源的程式碼寫入finally塊中。
必須注意的是,在finally塊中不能丟擲異常。JAVA異常處理機制保證無論在任何情況下必須先執行finally塊然後在離開try塊,因此在try塊中發生異常的時候,JAVA虛擬機器先轉到finally塊執行finally塊中的程式碼,finally塊執行完畢後,再向外丟擲異常。如果在finally塊中丟擲異常,try塊捕捉的異常就不能丟擲,外部捕捉到的異常就是finally塊中的異常資訊,而try塊中發生的真正的異常堆疊資訊則丟失了。
請看下面的程式碼:

Connection  con = null;
try
{
    con = dataSource.getConnection();
    ……
}
catch(SQLException e)
{
    ……
    throw e;//進行一些處理後再將資料庫異常丟擲給呼叫者處理
}
finally
{
    try
    {
        con.close();
    }
    catch(SQLException e)
{
    e.printStackTrace();
    ……
}
}
執行程式後,呼叫者得到的資訊如下
java.lang.NullPointerException
 at myPackage.MyClass.method1(methodl.java:266)
而不是我們期望得到的資料庫異常。這是因為這裡的con是null的關係,在finally語句中丟擲了NullPointerException,在finally塊中增加對con是否為null的判斷可以避免產生這種情況。

4. 異常不能影響物件的狀態
異常產生後不能影響物件的狀態,這是異常處理中的一條重要規則。 在一個函式
中發生異常後,物件的狀態應該和呼叫這個函式之前保持一致,以確保物件處於正確的狀態中。
如果物件是不可變物件(不可變物件指呼叫建構函式建立後就不能改變的物件,即
    建立後沒有任何方法可以改變物件的狀態),那麼異常發生後物件狀態肯定不會改變。如果是可變物件,必須在程式設計中注意保證異常不會影響物件狀態。有三個方法可以達到這個目的:
(1) 將可能產生異常的程式碼和改變物件狀態的程式碼分開,先執行可能產生異常的程式碼,如果產生異常,就不執行改變物件狀態的程式碼。
(2) 對不容易分離產生異常程式碼和改變物件狀態程式碼的方法,定義一個recover方法,在異常產生後呼叫recover方法修復被改變的類變數,恢復方法呼叫前的類狀態。
(3) 在方法中使用物件的拷貝,這樣當異常發生後,被影響的只是拷貝,物件本身不會受到影響。

5. 丟失的異常
請看下面的程式碼:
public void method2()
{
try
{
    ……
    method1();  //method1進行了資料庫操作
}
catch(SQLException e)
{
    ……
    throw new MyException(“發生了資料庫異常:”+e.getMessage);
}
}
public void method3()
{
    try
{
    method2();
}
catch(MyException e)
{
    e.printStackTrace();
    ……
}
}
上面method2的程式碼中,try塊捕獲method1丟擲的資料庫異常SQLException後,丟擲了新的自定義異常MyException。這段程式碼是否並沒有什麼問題,但看一下控制檯的輸出:
MyException:發生了資料庫異常:物件名稱 'MyTable' 無效。
at MyClass.method2(MyClass.java:232)
at MyClass.method3(MyClass.java:255)
原始異常SQLException的資訊丟失了,這裡只能看到method2裡面定義的MyException的堆疊情況;而method1中發生的資料庫異常的堆疊則看不到,如何排錯呢,只有在method1的程式碼行中一行行去尋找資料庫操作語句了,祈禱method1的方法體短一些吧。
JDK的開發者們也意識到了這個情況,在JDK 1.4.1中,Throwable類增加了兩個構造方法,public Throwable(Throwable cause)和public Throwable(String message,Throwable cause),在建構函式中傳入的原始異常堆疊資訊將會在printStackTrace方法中列印出來。但對於還在使用JDK1.3的程式設計師,就只能自己實現列印原始異常堆疊資訊的功能了。實現過程也很簡單,只需要在自定義的異常類中增加一個原始異常欄位,在建構函式中傳入原始異常,然後過載printStackTrace方法,首先呼叫類中儲存的原始異常的printStackTrace方法,然後再呼叫super.printStackTrace方法就可以列印出原始異常資訊了。可以這樣定義前面程式碼中出現的MyException類:
public class MyExceptionextends Exception
{
    //建構函式
    public SMException(Throwable cause)
    {
        this.cause_ = cause;
    }

    public MyException(String s,Throwable cause)
    {
        super(s);
        this.cause_ = cause;
    }
    //過載printStackTrace方法,列印出原始異常堆疊資訊
    public void printStackTrace()
    {
        if (cause_ != null)
        {
            cause_.printStackTrace();
        }
        super.printStackTrace(s);
    }

    public void printStackTrace(PrintStream s)
    {
        if (cause_ != null)
        {
            cause_.printStackTrace(s);
        }
        super.printStackTrace(s);
    }

    public void printStackTrace(PrintWriter s)
    {
        if (cause_ != null)
        {
            cause_.printStackTrace(s);
        }
        super.printStackTrace(s);
    }
     //原始異常
     private Throwable cause_;
}

6. 不要使用同時使用異常機制和返回值來進行異常處理
下面是我們專案中的一段程式碼
try
{
    doSomething();
}
catch(MyException e)
{
if(e.getErrcode == -1)
{
    ……
}
if(e.getErrcode == -2)
{
   ……
}
……
}
假如在過一段時間後來看這段程式碼,你能弄明白是什麼意思嗎?混合使用JAVA異常處理機制和返回值使程式的異常處理部分變得“醜陋不堪”,並難以理解。如果有多種不同的異常情況,就定義多種不同的異常,而不要像上面程式碼那樣綜合使用Exception和返回值。
修改後的正確程式碼如下:
try
{
    doSomething();  //丟擲MyExceptionA和MyExceptionB
}
catch(MyExceptionA e)
{
……
}
catch(MyExceptionB e)
{
    ……
}

 

 

總結一下,

java執行時異常是可能在java虛擬機器正常工作時丟擲的異常。

java提供了兩種異常機制。一種是執行時異常(RuntimeExepction),一種是檢查式異常(checked execption)。

檢查式異常:我們經常遇到的IO異常及sql異常就屬於檢查式異常。對於這種異常,java編譯器要求我們必須對出現的這些異常進行catch 所以 面對這種異常不管我們是否願意,只能自己去寫一堆catch來捕捉這些異常。

執行時異常:我們可以不處理。當出現這樣的異常時,總是由虛擬機器接管。比如:我們從來沒有人去處理過NullPointerException異常,它就是執行時異常,並且這種異常還是最常見的異常之一。

RuntimeExecption在java.lang包下,

下面是由java虛擬機器提供的執行時異常

AnnotationTypeMismatchException, 
ArithmeticException, 
ArrayStoreException, 
BufferOverflowException, 
BufferUnderflowException, 
CannotRedoException, 
CannotUndoException, 
ClassCastException, 
CMMException, 
ConcurrentModificationException, 
DOMException, 
EmptyStackException, 
EnumConstantNotPresentException, 
EventException, 
IllegalArgumentException, 
IllegalMonitorStateException, 
IllegalPathStateException, 
IllegalStateException, 
ImagingOpException, 
IncompleteAnnotationException, 
IndexOutOfBoundsException, 
JMRuntimeException, 
LSException, 
MalformedParameterizedTypeException, 
MirroredTypeException, 
MirroredTypesException, 
MissingResourceException, 
NegativeArraySizeException, 
NoSuchElementException, 
NoSuchMechanismException, 
NullPointerException, 
ProfileDataException, 
ProviderException, 
RasterFormatException, 
RejectedExecutionException, 
SecurityException, 
SystemException, 
TypeConstraintException, 
TypeNotPresentException, 
UndeclaredThrowableException, 
UnknownAnnotationValueException, 
UnknownElementException, 
UnknownTypeException, 
UnmodifiableSetException, 
UnsupportedOperationException, 
WebServiceException 

 

看到這麼多異常,想要找出我們常見的5中執行時異常是非常容易的。  

例如:ClassCastException(類轉換異常)

IndexOutOfBoundsException(陣列越界)

NullPointerException(空指標)

ArrayStoreException(資料儲存異常,運算元組時型別不一致)

還有IO操作的BufferOverflowException異常

 

1.throws 用於丟擲方法層次的異常, 
並且直接由些方法呼叫異常處理類來處理該異常, 
所以它常用在方法的後面。比如 
public static void main(String[] args) throws SQLException

2.throw 用於方法塊裡面的程式碼,比throws的層次要低,比如try...catch ....語句塊,表示它丟擲異常, 
但它不會處理它, 
而是由方法塊的throws Exception來呼叫異常處理類來處理。

throw用在程式中,明確表示這裡丟擲一個異常。   
throws用在方法宣告的地方,表示這個方法可能會丟擲某異常。

throw是丟擲一個具體的異常類,產生一個異常。
throws則是在方法名後標出該方法會產生何種異常需要方法的使用者捕獲並處理。

 

 




相關文章