1. 異常
-
(P 280)異常處理需要考慮的問題:
- 使用者輸入錯誤
- 裝置錯誤
- 物理限制
- 程式碼錯誤
-
(P 280)傳統的處理錯誤的方法是:返回一個特殊的錯誤碼,常見的是返回-1或者
null
引用 -
(P 280)在Java中,方法出現錯誤時,它會立即退出,不返回任何值,而是丟擲一個封裝了錯誤資訊的物件
-
(P 280)Java中所有的異常都是由
Throwable
繼承而來,它下面又分解為兩個分支:Error
和Exception
Error
:描述了Java執行時系統的內部錯誤和資源耗盡錯誤(對於處理這種錯誤,你幾乎無能為力)Exception
:又分解為兩個分支:RuntimeException
(由程式設計錯誤導致,如果出現該異常,那一定是你的問題)和其他異常(諸如IO錯誤這類問題)- 派生於
RuntimeException
的異常:- 錯誤的強制型別轉換
- 陣列訪問越界
- 訪問
null
指標
- 不是派生於
RuntimeException
的異常:- 試圖超越檔案末尾繼續讀取資料
- 試圖開啟一個不存在的檔案
- 試圖根據特定的字串查詢
Class
物件,而這個字串表示的類並不存在
- 派生於
graph TD Throwable[Throwable]-->Error[Error] Throwable[Throwable]-->Exception[Exception] Error[Error]-->OtherError1[...] Error[Error]-->OtherError2[...] Error[Error]-->OtherError3[...] Exception[Exception]-->RuntimeException[RuntimeException] Exception[Exception]-->IOException[IOException] Exception[Exception]-->OtherException[...] IOException[IOException]-->OtherIOException1[...] IOException[IOException]-->OtherIOException2[...] IOException[IOException]-->OtherIOException3[...] RuntimeException[RuntimeException]-->OtherRuntimeException1[...] RuntimeException[RuntimeException]-->OtherRuntimeException2[...] RuntimeException[RuntimeException]-->OtherRuntimeException3[...] -
(P 281)派生於
Error
和RuntimeException
的所有異常稱為非檢查型異常,所有其他異常稱為檢查型異常 -
(P 282)如果沒有處理器捕獲異常物件,那麼當前執行的執行緒就會終止
-
(P 283)必須在方法的首部列出所有檢查型異常型別,但是不需要宣告從
Error
繼承的異常,也不應該宣告從RuntimeException
繼承的那些非檢查型異常 -
(P 283)如果在子類中覆蓋了超類的一個方法,子類方法中宣告的檢查型異常不能比超類方法中宣告的異常更通用(子類方法可以丟擲更特定的異常,或者根本不丟擲任何異常)。如果超類方法沒有丟擲任何檢查型異常,子類也不能丟擲任何檢查型異常
-
(P 288)同一個
catch
子句中可以捕獲多個異常型別,如果一些異常的處理邏輯是一樣的,就可以合併catch
子句。只有當捕獲的異常型別彼此之間不存在子類關係時才需要這個特性try { ... } catch (FileNotFoundException | UnknownHostException e) { ... } catch (IOException e) { ... }
-
(P 289)可以在catch子句中丟擲一個異常,此時,可以把原始異常設定為新異常的“原因”
try { ... } catch (SQLException original) { var e = new ServletException("database error"); e.initCause(original); throw e; }
捕獲異常時,獲取原始異常
Throwable original = caughtException.getCause();
-
(P 292)一種推薦的異常捕獲寫法:內層
try
語句塊只有一個職責,就是確保釋放資源,外層try
語句塊也只有一個職責,就是確保報告出現的錯誤try { try { ... } finally { // 釋放資源 } } catch (Exception e) { // 報告錯誤 }
-
(P 293)Java 7中,對於實現了
AutoCloseable
介面的類,可以使用帶資源的try
語句(try-with-resources):try (Resources res = ...) { // Work with res ... }
Java 9中,可以在
try
首部中提供之前宣告的事實最終變數(effectively final variable):try (res) { // Work with res ... } // res.close() called here
-
(P 294)在try-with-resources語句中,如果
try
塊丟擲一個異常,而且close
方法也丟擲一個異常,則原來的異常會重新丟擲,而close
方法丟擲的異常會“被抑制”(可以通過getSuppressed
方法得到這些被抑制的異常) -
(P 294)可以通過
StackWalker
類處理堆疊軌跡var walker = StackWalker.getInstance(); walker.forEach(frame -> ...); // 例如:walker.forEach(System.out::println);
-
(P 298)使用異常的一些技巧:
- 異常處理不能代替簡單的測試(捕獲處理異常的成本很高,只在異常情況下使用異常)
- 不要過分的細化異常(有必要將整個任務包在一個
try
語句塊中,將正常處理與錯誤處理分開) - 充分利用異常層次結構
- 不要壓制異常(異常非常重要時,應該適當地進行處理)
- 在檢測錯誤時,“苛刻”要比放任更好
- 不要羞於傳遞異常(最好繼續傳遞異常,而不是自己捕獲)
2. 斷言
-
(P 301)Java中引入了關鍵字
assert
,其有如下兩種形式:assert condition; assert condition : expression;
這兩個語句都會計算條件,如果結果為
false
,則丟擲一個AssertionError
異常。在第二個語句中,表示式將傳入AssertionError
物件的構造器,並轉換為一個訊息字串 -
(P 301)預設情況下,斷言是禁用的,可以使用
-enableassertions
或者-ea
選項啟用斷言:java -enableassertions MyApp
禁用斷言可以使用
-disableassertions
或-da
-
(P 302)斷言只應該用於在測試階段確定程式內部錯誤的位置
3. 日誌
-
(P 305)基本日誌的使用:
-
生成簡單的日誌記錄
Logger.getGlobal().info("hello world!");
-
取消所有日誌
Logger.getGlobal().setLevel(Level.OFF);
-
-
(P 305)高階日誌的使用:
-
建立或獲取日誌記錄器
private static final Logger myLogger = Logger.getLogger("className"); // className是全限定類名
-
設定日誌級別
logger.setLevel(Level.FINE); // FINE以及更高階別的日誌都會被記錄
-
記錄日誌
// 呼叫相應級別的日誌記錄方法 logger.warning(message); logger.fine(message); // 使用log方法並指定級別 logger.log(Level.FINE, message); // 跟蹤執行流的方法 logger.entering("className", "methodName", new Object[]{ params... }); logger.exiting("className", "methodName", result); // 在日誌記錄中包含異常的描述 logger.throwing("className", "methodName", exception); logger.log(Level.WARNING, message, exception);
-
-
(P 305)7個日誌級別:
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
-
(P 307)可以通過配置檔案修改日誌系統的各個屬性,預設情況下,配置檔案位於:
conf/logging.properties
指定特定位置的配置檔案:
java -Djava.util.logging.config.file=configFile MainClass
指定日誌記錄器的日誌級別:在日誌記錄器名後面追加字尾.level,例如
com.mycompany.myapp.level=FINE
-
(P 313)日誌技巧
- 對一個簡單的應用,選擇一個日誌記錄器,可以把日誌記錄器命名為與主應用包一樣的名字
- 預設的日誌配置會把級別等於或高於INFO的所有訊息記錄到控制檯,使用者可以覆蓋這個預設配置,最好在你的應用中安裝一個更合適的預設配置
- 所有級別為INFO、WARNING和SEVERE的訊息都將顯示到控制檯上
- 只將對程式使用者有意義的訊息設定為以上這幾個級別
- 將程式設計師想要的日誌訊息設定為FINE級別是一個很好的選擇
-
(P 321)除錯技巧
-
列印或日誌記錄變數的值
-
在每一個類中放置一個單獨的
main
方法,以便獨立地測試類 -
使用
JUnit
-
日誌代理,它是一個子類的物件,可以截獲方法呼叫,記錄日誌,然後呼叫超類中的方法
var generator = new Random() { public double nextDouble() { double result = super.nextDouble(); Logger.getGlobal().info("nextDouble: " + result); return result; } }
-
利用
Throwable
類的printStackTrace
方法,可以從任意的異常物件獲得堆疊軌跡 -
一般來說,堆疊軌跡顯示在
System.err
上。如果想要記錄或顯示堆疊軌跡,可以將它捕獲到一個字串中var out = new StringWriter(); new Throwable().printStackTrace(new PrintWriter(out)); String description = out.toString();
-
通常,將程式錯誤記入一個檔案會很有用:
java MyProgram > errors.txt # 錯誤,錯誤被髮送到System.err而不是System.out java MyProgram 2> errors.txt # 正確,只輸出System.err java MyProgram 1> errors.txt 2>&1 # 同時捕獲System.out和System.err
-
將未捕獲的異常的堆疊軌跡記錄到一個檔案中,而不是直接輸出到
System.err
,可以使用靜態方法Thread.setDefaultUncaughtExceptionHandler
改變未捕獲異常的處理器Thread.setDefaultUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { // save information in log file } } )
-
要想觀察類的載入過程,啟動Java虛擬機器時可以使用
-verbose
標誌 -
-Xlint
選項告訴編譯器找出常見的程式碼問題javac -Xlint sourceFiles
-
Java虛擬機器增加了對Java應用程式的監控和管理支援,允許在虛擬機器中安裝代理來跟蹤記憶體消耗、執行緒使用、類載入等情況。jconsole工具可以顯示有關虛擬機器效能的統計結果
-
Java任務控制器:一個專業級效能分析和診斷工具
-