1. 程式人生 > >《java程式設計思想》 第十二章異常處理錯誤

《java程式設計思想》 第十二章異常處理錯誤

12.4

之前程式裡寫日誌不清楚怎麼把printStackTrace()輸出的內容寫到日誌裡,僅僅是寫getMessage()資訊少了不少。在本節的例子中給出了一個方法:

StringWriter sw = new StringWriter();

PrintWriter pw = new PrintWriter(sw);

e.printStrackTrace(pw);

logger.error(sw.toString());

12.5

可以宣告方法將異常丟擲,但實際上該方法並不丟擲異常。這樣做的好處是為異常先佔一個位子,以後可以丟擲這種異常而不用修改方法宣告。

在編譯時被強制檢查的異常稱為被檢查的異常。

練習8題中說“丟擲練習3裡定義的異常”錯了,應該是練習4中定義的異常。中有個細節需要注意:宣告方法時標識了丟擲的異常,即使方法內部並沒有真正丟擲此異常,呼叫該方法的時還是要處理異常,否則編譯器會報錯。

12.6

Exception是與程式設計有關的所有異常類的基類。它從Throwable類中繼承了一些方法:

String getMessage()

String getLocalizedMessage()

獲取異常資訊和用本地語言表示的異常資訊。

void printStackTrace()

void printStackTrace(PrintStream)

void printStackTrace(PrintWriter)

列印資訊和呼叫棧軌跡。第一個版本輸出到標準錯誤流,後兩個版本可以選擇要輸出的流。

Throwable fillInStrackTrace()這個方法的作用是對呼叫的物件重新填充呼叫棧,使得呼叫棧看起來和新建立的異常是相同的。經過編碼測試,方法返回的Throwable的物件和呼叫物件是同一個物件,看來只是改變了呼叫棧的資料,以下是測試程式碼:

public class FilInStackTraceTest {

	public void f() throws Exception {
		throw new Exception();
	}
	
	public void g() {
		try
{ f(); } catch (Exception e) { e.printStackTrace(); Exception e1 = (Exception)e.fillInStackTrace(); System.out.println(e1 == e); e.printStackTrace(); e1.printStackTrace(); } } public static void main(String[] args) { FilInStackTraceTest test = new FilInStackTraceTest(); test.g(); } }

輸出結果如下:

java.lang.Exception
    at com.sfauto.exception.FilInStackTraceTest.f(FilInStackTraceTest.java:6)
    at com.sfauto.exception.FilInStackTraceTest.g(FilInStackTraceTest.java:11)
    at com.sfauto.exception.FilInStackTraceTest.main(FilInStackTraceTest.java:23)
java.lang.Exception
    at com.sfauto.exception.FilInStackTraceTest.g(FilInStackTraceTest.java:14)
    at com.sfauto.exception.FilInStackTraceTest.main(FilInStackTraceTest.java:23)
java.lang.Exception
    at com.sfauto.exception.FilInStackTraceTest.g(FilInStackTraceTest.java:14)
    at com.sfauto.exception.FilInStackTraceTest.main(FilInStackTraceTest.java:23)
true

從最後一行可以看出,e與e1是一個物件,呼叫fillInStackTrace()方法之前和之後e.printStackTrace()的列印結果不同,而呼叫filllInStackTrace()方法之後e與e1printStackTrace()的列印同。由此可見fillInStackTrace()是改變了呼叫物件的呼叫棧。

printStackTrace()方法所提供的資訊可以通過getStackTrace()方法來獲取,此方法將返回一個數組,每一個元素都表示棧中的一幀。元素0是棧頂元素,是呼叫序列中的最後一個方法呼叫(即Throwable被建立和丟擲之處,離異常最近的方法),陣列中最後一個元素是棧底元素,即呼叫的最外層方法。

常常會想要在捕獲一個異常後丟擲另一個異常,並且希望把原始異常的資訊儲存下來,這被稱為異常鏈。Throwable和它的一些子類提供了帶有引數Throwable cause的建構函式來維持異常鏈。書中說只有Error、Exception和RuntimeException提供這個建構函式,好像說的不對,至少我知道的SQLException和IOException都有這種建構函式。在沒有此種建構函式的情況下可以呼叫initCause()方法來達到相同的效果。

12.7

Throwable這個Java類被用來表示任何可以作為異常被丟擲的類。Throwable物件可以分為兩種型別:Error用來表示編譯時錯誤和系統錯誤,除特殊情況外不用關心;Exception是與程式設計打交道的基本異常型別,在Java類庫、使用者方法以及執行時故障中都可能丟擲Exception異常。所以Java程式設計師關心的基本型別通常是Exception。

異常的基本概念是用名稱代表發生的問題,異常的名稱可以望文生義。異常並非全在java.lang包中,還存在於util、net和io包中。

有一些問題屬於Java的標準執行時檢測的一部分,它們會自動被Java虛擬機器丟擲,所以不必在方法的異常說明中把它們列出來,這樣的異常被稱為不受檢查的異常(也有叫執行時異常的吧),它們都是RuntimeException的子類。這種異常屬於錯誤,不強制要求手動捕獲,可以再自己的程式碼中丟擲這種異常。如果不捕獲這種異常,它會穿越所有的執行路徑直達main()方法,在主程式退出前將呼叫異常的printStackTrace()方法。

RuntimeException代表的是程式設計錯誤:

(1)無法預料的錯誤。比如從你的程式碼控制範圍之外傳遞近來的Null引用;

(2)應該在程式碼中檢查的錯誤,比如陣列越界。

12.8

Java中的異常不允許回到異常丟擲的地點,如果想實現這一功能可以把try快放到迴圈中,這就建立了一個“程式繼續執行之前必須要達到”的條件,還可以加入一個static型別的計數器或者別的裝置,使迴圈在放棄以前能嘗試一定的次數。

當涉及到break和continue語句的時候,finally子句也會得到執行。

當try塊中包含return語句,其後的finally塊也會被執行。

12.8.3小節中作者演示了兩種不恰當的方法使得一些異常被忽略,值得注意

第一種問題的解決方法

12.9

當覆蓋方法時,只能丟擲在基類方法的異常說明中列出的異常,可以少丟擲或不丟擲這些異常,也可以丟擲這些異常的子類,或者不丟擲異常。但是不能新增新的異常。即某個方法的異常說明範圍可以變小但是不能變大。

異常限制對構造器不起作用。子類的構造器可以丟擲任何新異常。但是因為基類構造器必須以這樣或那樣的方法被呼叫,派生類建構函式的異常說明必須包含基類建構函式的異常說明。

派生類建構函式不能捕獲基類建構函式丟擲的異常。

12.10

對於丟擲異常的建構函式,作者認為應該這樣處理:在一個單獨try-catch語句中構造物件,一旦物件構造成功(即建構函式未丟擲異常)用另外的巢狀try-catch-finally語句寫其他功能,在finally中記得清理該物件的資源。

12.11

練習25中,子類覆蓋了父類的方法並且丟擲了比父類方法更窄的異常,當我們建立了一個子類的物件,並將其向上轉型為父類,呼叫該方法編譯器會強制要求捕獲父類的異常。

12.12

對於一些不知道怎麼處理的被檢查異常,作者推薦兩種辦法:

(1)在main()函式中丟擲這些異常;

(2)利用異常鏈,把被檢查異常包裝成RuntimeException丟擲 

    throw new RuntimeException(e);

補充(前兩條來自《Java 8程式設計參考官方教程》,第三條官方教程看不明白,參考了以下部落格http://blog.csdn.net/jackiehff/article/details/17839225,之前的例子理解了,最後的論述依舊不是很懂

1.7版本,異常系統新增的三個新特性:

1、帶資源的try

這種特性有時被稱為自動資源管理(Automatic Resource Management,ARM)

try語句的形式:

try(資源定義和初始化) {

}

在資源定義和初始化語句中宣告的變數當try語句塊結束時,自動釋放資源。只有實現了AutoCloseable介面的資源才能使用帶資源的try語句,該介面定義了close()方法,try語句塊結束的時候會呼叫資源的close()方法。

try語句中宣告的資源被隱式的宣告為fianl,這意味著在建立資源變數後不能將其他變數賦值給該引用。另外,資源的作用域侷限於帶資源的try語句。

可以再一條try語句中管理多個資源。為此,只需要簡單的使用分號分隔每個資源即可。

關閉資源的close()方法也可能丟擲異常,使用帶資源的try語句時,當try語句塊中丟擲異常同時close()方法也丟擲異常,close()方法丟擲的異常會被抑制,但它並沒有丟失,而是被新增到第一個異常的抑制列表中,使用Throwable類定義的getSupperessed()方法可以獲取抑制異常列表。

2、多重捕獲

允許通過相同的catch子句捕獲多個異常,使用操作符 | 分隔每個異常。每個多重捕獲引數都被隱式的宣告為final,因此不能賦予它新的值。正常的捕獲並沒有final的限制,我想是因為多重捕獲形式如下:

catch(IOException | ArrayIndexOutOfBoundsException e ) {

}

如果要在catch語句塊中重新賦值,編譯器搞不清楚e到底是第一個型別還是第二個型別。

3、重新丟擲精確的異常

考慮下面的例子:

static class FirstException extends Exception { }
  static class SecondException extends Exception { }

  public void rethrowException(String exceptionName) throws Exception {
    try {
      if (exceptionName.equals("First")) {
        throw new FirstException();
      } else {
        throw new SecondException();
      }
    } catch (Exception e) {
      throw e;
    }
  }

這個例子的 try 塊既可以丟擲 FirstException 也可以丟擲 SecondException 。假定你想要在rethrowException方法宣告中的 throws  子句中指定異常型別為這兩種型別,在Java SE 7之前的版本中你不能這麼做。因為 catch 子句的異常引數e是 Exception型別,並且 catch塊重新丟擲這個異常引數 e,所以你只能在rethrowException方法宣告中的 throws 子句中指定異常型別為  Exception  。

然而在Java SE 7中, 你可以在rethrowException方法宣告中的 throws 子句中指定異常型別為  FirstException 和 SecondException  。 Java SE 7編譯器可以探測到由 throw e  語句丟擲的異常必須來自於 try 塊, 並且 try 塊丟擲的異常只能是 FirstException 和 SecondException。即使 catch 子句的異常引數e的型別是 Exception,編譯器也可以探測到它是 FirstException 還是 SecondException 的例項:

public void rethrowException(String exceptionName)
  throws FirstException, SecondException {
    try {
      // ...
    }
    catch (Exception e) {
      throw e;
    }
  }

如果 catch 塊中的 catch 引數被指定給另一個值,那麼這種分析失效。然而,如果的 catch 引數被指定給另一個值, 你必須在方法宣告的 throws 子句中指定異常型別為 Exception 。

具體說來,在Java SE 7及後續版本中, 當你在一個 catch 子句中宣告一個或多個異常型別並且重新丟擲由這個 catch 塊處理的異常,編譯器會驗證重新丟擲的異常型別是否滿足以下條件:

  • try 塊可以丟擲它。
  • 先前的  catch  塊沒有辦法處理它。
  • 它是  catch  子句其中一個異常引數的子類或者超類。