Java核心技術讀書筆記7-1 Java異常
1.異常
1.1 處理錯誤
異常處理的任務就是將控制源從錯誤產生的地方轉移給能夠處理這種情況的錯誤處理器。
1.2 異常分類
在Java中,所有異常物件都是派生於Throwable類的一個例項。
對於Throwable類的兩個子類:
Error類:描述了Java執行時系統內部錯誤和資源耗盡錯誤。應用程式不應該丟擲這種型別的物件。所以若出現這種內部錯誤,除了告知使用者並盡力使程式安全終止別無他法。
Exception類:需要關注這類錯誤,對於其兩個子類,RuntimeException類表示了由程式錯誤導致的問題(出現這種問題一定是你程式編寫的問題),IOException則描述了I/O相關錯誤。
Java將派生於Error類或RuntimeException類的所有異常稱為非受查異常
1.3 建立一個自己的異常類:如果系統中的異常都無法滿足描述自己程式碼中出現異常的場景,那麼就可以自己構建一個異常類,然後用該類繼承一個異常類。
public class MyException extends RuntimeException{ public MyException(){ new MyException(""); } public MyException(String message){ super(message); System.out.println("-----myException-----"); } }
1.4 異常宣告與處理
—丟擲異常:一個方法不僅要告訴編譯器返回什麼值,還要告訴編譯器可能發生什麼錯誤。
throws關鍵字:對於方法可能會出現的受查異常,可以用throws關鍵字對其進行丟擲告知編譯器並交給呼叫者處理。這是系統便會搜尋對應的異常處理器,以便知道如何處理異常物件。若有多個異常丟擲則使用逗號分隔。若子類覆蓋了超類的一個方法,子類方法中宣告的受查異常不能比超類方法總宣告的異常更通用(即子類應該更明確的丟擲某種特定的異常),所以如果超類沒有丟擲異常,子類一不應該丟擲異常。
public void aMethod(parameters..) throws ExceptionA, ExceptionB
throw關鍵字:如果我們想在程式碼丟擲一個指定的異常可以使用該關鍵字。丟擲後需要在方法後使用throws宣告丟擲的異常。
public class ThrowTest {
public void testMethod() throws RuntimeException { //宣告的異常必須大於等於實際丟擲異常的級別
boolean flag;
System.out.println("do something...");
flag = false; //some things are not normal
System.out.println("If something goes wrong, I want to throw an exception");
if(!flag){
throw new MyException("flag = " + flag + " means Some things are not normal"); //括號內可以傳入一個字串資訊
}
}
public static void main(String[] args) throws Exception {
new ThrowTest().testMethod();
}
}
—捕獲異常:有些程式碼必須捕獲異常,然後對異常進行處理。如果異常發生是沒有在任何地方進行捕獲,那麼程式就會終止執行,並在控制檯打印出異常資訊,包括異常的型別和堆疊資訊。
try/catch
try{
codes..
}
catch(ExceptiohType e)
{
handler for this type
}
若try語句塊中沒有丟擲任何異常,則跳過catch子句;
若try語句塊中任何程式碼丟擲了一個在catch子句中說明的異常類,那麼:
1)跳過try語句塊中其餘程式碼
2)執行catch子句中的程式碼
若try語句塊中程式碼丟擲了一個在catch子句中沒有說明的異常類,那麼方法立刻退出。
丟擲or捕獲? 應該捕獲那些知道如何處理的異常,將不知道怎樣處理的異常繼續進行傳遞。但若出現超類沒丟擲異常這種情況,則子類必須捕獲每一個可能出現的異常。
捕獲多個異常:若程式可能出現多個異常,且不同異常的處理方式也可能不同,那麼應按照從低到高的順序依次書寫catch子句進行捕獲(否則,若將最高級別異常catch放到最上面,則只要出現任何低於該級別的異常將會都被該句捕獲)。
try{
codes..
}
catch(最低級別異常型別 e)
{
handler for this type
}
catch(相同處理方式異常型別1| 相同處理方式異常型別2 e) //相同處理型別的異常可以寫到一個catch中,異常型別用"|"分隔
{
handler for this type
}
catch(可能出現的最高級別異常 e)
{
handler for this type
}
捕獲多個異常變數時,該變數隱含final屬性,不可更改其值
捕獲異常重新丟擲:重新丟擲的目的是為了改變異常型別
try{
boolean flag;
System.out.println("do something...");
flag = false; //some things are not normal
System.out.println("If something goes wrong, I want to throw an exception");
if(!flag){
throw new MyException("flag = " + flag + " means Some things are not normal"); //括號內可以傳入一個字串資訊
}
}catch (MyException e){
// e.printStackTrace(); //列印異常的堆疊資訊
// throw new RuntimeException("產生了一個自定義異常 " + e.getMessage()); //使用一個異常文字構造: 產生了一個自定義異常 flag = false means Some things are not normal
Exception se = new RuntimeException("產生了自定義異常"); //更高階的方法,可以丟擲一個高階異常,但不會丟原異常的細節
se.initCause(e);
throw se;
}
finally子句:若程式碼丟擲了一個異常,就會終止方法中剩餘程式碼的處理並退出方法。若方法獲得了一些本地資源,那麼這些資源應該方法退出之前被回收。此時,若果出現異常就產生了資源回收問題。
InputStream is = new FileInputStream(..);
try
{
...
}catch(IOException e) //try語句可以只有finally子句而沒有catch子句
{
...
}finally{
in.close;
}
對於異常丟擲和finally語句有兩個原則:
1)finally塊內的語句一定會在所在語句塊(try/catch)的最後執行(含有return除外),無論是否出現異常以及異常是否繼續丟擲。
2)若當前塊中程式碼出現異常,則當前塊其餘程式碼立即終止執行並丟擲異常。丟擲的異常交由本層程式碼塊的catch子句處理否則向外層傳遞。
那麼若果catch語句中有return語句且未發生異常的情況下該怎麼辦呢?
1)若finally塊中同樣含有return語句,則finally中的return覆蓋catch中的return。
2)否則,catch中return會在finally語句塊程式碼執行完畢後執行。
帶資源的try語句(try-with-resorces)
try(Resource res = ...)
{
...
}
該語句在java7引入,括號中使用的資源變數所屬的類需要實現AutoCloseable介面,該介面包含一個可以丟擲Exception異常的close()方法(另有一個Closeable介面,丟擲的是IOException),多個資源有分號;隔開。
該語句相當於實現了一個finally結構,括號中的資源會無論會不會發生異常都會關閉,同時對於關閉資源可能會產生的異常也有很好的處理。若關閉失敗則丟擲異常,若try語句塊中與close都出現異常並丟擲,則會把close丟擲的異常抑制,然後使用addSuppressed(Throwable t)方法增加到原來的異常,而原來的異常會正常丟擲。
Throwable[] getSuppressed();可以得到呼叫異常物件的所有被抑制異常。
1.5 堆疊軌跡(stack trace):是一個方法呼叫過程的列表,包含了程式執行過程中方法呼叫的特定位置。下面為獲取方法:
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces(); //獲取所有執行緒的堆疊軌跡對映。key為執行緒,value對應執行緒的堆疊元素陣列
new Throwable().getStackTrace(); //獲取堆疊元素陣列
new Throwable.printStackTrace([A outStream]); //列印堆疊資訊到一個輸出流中,如無輸出流引數則預設輸出到標準錯誤輸出流System.err中。
對於堆疊元素,可以使用如下方法獲取資訊: