Java基礎系列(三十二):斷言 + 日誌入門
斷言
在Java語言中,給出了3種處理系統錯誤的機制:
- 丟擲一個異常
- 日誌
- 使用斷言
那我們應該在什麼情況下去使用斷言呢?
- 斷言失敗是致命的,不可恢復的錯誤
- 斷言失敗只用於開發和測試階段。
不應該使用斷言向程式的其他部分通告發生了可恢復性的錯誤,或者,不應該作為程式向用戶通告問題的手段,斷言只應該用於在測試階段確定程式內部的錯誤資訊。
在一個具有自我保護能力的程式中,斷言很常用,假如確信某個屬性符合要求,並且程式碼的執行非常的依賴這個屬性,比如:
double a = Math.sqrt(x);
我們在這裡確信x必須是一個正值,因為它是另一個計算的得出的非負結果,或者是某一個方法的引數,而這個方法要求它的呼叫者只能提供一個正整數。但是為了以防萬一,我們還是會對這個引數進行檢查:
if(x < 0) {
throw new IllegalArgumentException("x < 0");
}
但是,有一個問題就是,這段程式碼會一直保留在程式中,即使測試完畢也不會自動的進行刪除,如果程式中含有大量的這樣的檢查,會嚴重的影響程式的執行速度。
而斷言機制允許在測試期間向程式碼中插入一些檢查語句。當代嗎釋出的時候,這些插入的檢測語句將會自動地移走。
在Java中,斷言有兩種語法形式:
assert 條件;
assert 條件:表示式;
這兩種形式都會對條件進行檢測,如果結果為false,就會丟擲一個AssertionError異常。在第二種形式中,表示式將會傳入AssertionError的構造器,並轉換成一個訊息字串。
在上述的程式中,如果我們想使用斷言:
assert x >= 0;
//或者將x的實際值傳給AssertionError物件
assert x >= : x;
但是在預設情況下,斷言是被禁用的,我們可以通過在執行程式的時候輸入引數來選擇啟用:
java -ea MyApp //or java -enableassertions MyApp
啟動和禁用斷言的時候不用重新編譯程式,它是類載入器的功能,當斷言被禁用的時候,類載入器將會跳過斷言程式碼,所以,不會降低程式的執行速度。
同樣的,我們也可以在某個類或整個包中使用斷言,比如:
java - ea:MyClass -ea:com.viyoung... MyApp
這個命令將會開啟MyClass類以及在com.viyoung包和它的子包中的所有類的斷言。
選項 -ea 將會開啟預設包中所有類的斷言。
也可以使用選項 -disableassertions 或 -da 禁用某個特定類或包的斷言:
java -ea: ... -da:MyClass MyApp
有些類不是由類載入器載入,而是直接由虛擬機器載入。可以使用這些開關有選擇的啟用或禁用那些類的斷言。
然而,啟用和禁用所有斷言的 -ea 和 -da 開關不能應用到那些沒有類載入器的“系統類”上,對於這些系統類來說,需要使用 -enablesystemassertions/-esa 開關啟用斷言。
斷言和日誌的區別在於,斷言是一種測試和除錯階段使用的戰術性工具;而日誌記錄是一種在程式的整個生命週期都可以使用的策略性工具。
記錄日誌
說起日誌,大家可能會有點陌生,尤其是剛剛接觸Java不久的初級程式設計師,我們在學習初期進行除錯程式的時候回插入一些System.out.println
方法來幫助我們對程式的執行狀況進行一個把控和分析,但是如果說,我們解決了這個問題,就需要把這些語句從我們的程式碼中及時的刪除,當遇到其他問題的時候,則需要再次新增,然後解決後再刪除,Java中內建了一個包叫做:java.util.logging
包中,在這個包中提供了一系列的API來解決我們的痛點,這些API的優點有許多:
- 可以很容易地取消全部日誌記錄,或者僅僅取消某個級別的日誌,而且開啟和關閉這個操作也是很容易的。
- 可以很簡單地禁止日誌記錄的輸出,因此,將這些日誌程式碼留在程式的開銷很小。
- 日誌記錄可以被定向到不同的處理器,用於在控制檯中顯示,用於儲存在檔案中等。
- 日誌記錄器和處理器都可以對記錄進行過濾。過濾器可以根據過濾實現器制定的標準丟棄那些無用的記錄項。
- 日誌記錄可以採用不同的方式格式化,例如,純文字或XML。
- 應用程式可以使用多個日誌記錄器,它們使用類似包名的這種具有層次結構的名字,例如,com.viyoung.myapp。
- 在預設情況下,日誌系統的配置由配置檔案控制。如果需要的話,應用程式可以替換這個配置。
基礎日誌
如果只是想生成一個簡單的日誌記錄,可以使用全域性日誌記錄器(global logger)並呼叫其info方法:
Logger.getGloabal().info("This is a Logger Info");
他會在控制檯上打印出:
INFO:This is a Logger Info
如果在適當的地方呼叫
Logger.getGlobal().setLevel(Level.OFF)
會取消所有的日誌。
高階日誌
上面的日誌在我們日常的開發中是不常見的,在一個專業的應用程式中,不要講所有的日誌都記錄到一個全域性日誌記錄器中,而是可以自定義日誌記錄器。
可以呼叫Logger
類的getLogger()
方法獲取記錄器:
private static final Logger myLogger = Logger.getLogger("com.viyoung.myapp");
未被任何變數引用的日誌記錄器都可能會被垃圾回收,為了防止這種情況的發生,所以要用一個靜態變數儲存日誌記錄器的一個引用。
與包名類似,日誌記錄器名也具有層次結構,而且與包名相比,日誌記錄器的層次結構更強,如果你對某個包設定了日誌級別,那麼它的子記錄器會去繼承這個級別。
通常來說,存在以下7個日誌記錄器級別:
1. SEVERE
2. WARINING
3. INFO
4. CONFIG
5. FINE
6. FINER
7. FINEST
通常來說,只會記錄前三個級別,但是也可以設定其他的級別。比如:
logger.setLevel(Level.FINE);
當然,我們還可以使用Level.ALL開啟所有級別的記錄,或者使用Level.OFF關閉所有級別的記錄。
對於所有級別有下面幾種記錄方法:
logger.warning(message);
logger.fine(message);
同時,還可以使用log方法指定級別,例如:
logger.log(Level.FINE, message);
預設的日誌配置記錄了INFO或更高級別的所有記錄,因此,應該使用CONFIG、FINE、FINER和FINESET級別來記錄那些有助於診斷,但對於程式設計師又沒有太大意義的除錯資訊。
預設的日誌記錄將顯示包含日誌呼叫的類名和方法名,如同堆疊所顯示的那樣,但是如果虛擬機器對執行過程進行了優化,就會導致獲取不到準確的呼叫資訊,這時我們可以使用logp
方法獲得呼叫類和方法的確切位置:
void logp(Level l, String className, String MethodName, String message);
以及有一些用來追蹤執行流的方法:
// 記錄一個方法條目
void entering(String className, String methodName);
//記錄一個方法條目,帶有一個引數。
void entering(String sourceClass, String sourceMethod, Object param1)
// 記錄一個方法條目,帶有一組引數。
void entering(String sourceClass, String sourceMethod, Object[] params)
//記錄一個方法返回。
void exiting(String sourceClass, String sourceMethod)
// 記錄一個方法返回,帶有結果物件。
void exiting(String sourceClass, String sourceMethod, Object result)
記錄日誌的常見用途是記錄那些不可預測的異常,可以使用下面的兩個方法提供日誌記錄中包含的異常描述內容:
//正丟擲異常的記錄。
void throwing(String sourceClass, String sourceMethod, Throwable thrown)
//記錄帶有相關的可丟擲資訊的訊息。
void log(Level level, String msg, Throwable thrown)
公眾號
掃碼或微信搜尋 Vi的技術部落格,關注公眾號,不定期送書活動各種福利~