Java錯誤處理、Java斷言和日誌
Java錯誤處理:
在計算機執行過程中,錯誤總是會出現:
使用者輸入的錯誤;
讀寫檔案的錯誤;
網路錯誤,記憶體耗盡等等。
如果一個方法調用出錯,呼叫方如何得知這個錯誤呢?
異常:
Java使用異常來表示錯誤。異常是class,本身帶有型別資訊。異常可以在任何地方丟擲。異常只需要在上層捕獲,和方法呼叫分離。
如:
Java的異常體系:
Java規定,必須捕獲的異常包括Exception及其子類,但不包括RuntimeException及其子類。
我們把Exception及其子類稱為Checked Exception。
不需要捕獲的異常包括Error及其子類,RuntimeException及其子類。
因為Error是發生了嚴重錯誤,程式一般對此無能為力。而Exception是發生了執行時的邏輯錯誤,應該捕獲異常並處理。
捕獲並處理錯誤:IOException,NumberFormatException;
修復程式:NullPointerException,IndexOutOfBoundsException。
我們使用try...catch捕獲異常,可能發生異常的語句放在try{...}中,使用catch捕獲對應的Exception及其子類。
如:
對可能丟擲Checked Exception的方法呼叫,我們可以捕獲Exception處理,如果不捕獲則可以用過throws宣告,但通過throws聲明後仍然需要在上層捕獲。main()方法是最後捕獲Exception的機會。如果仍沒有捕獲,JVM就會報錯並退出。
如:
總結:
Java使用異常來表示錯誤,並通過try{...}catch{...}捕獲異常;
Java的異常是class,並且從Throwable繼承;
Error是無需捕獲的嚴重錯誤;
Exception是應該捕獲的可處理的錯誤;
RuntimeException無需強制捕獲,非RunimeException(Checked Exception)需強制捕獲,或者用throws宣告。
捕獲異常:
我們可以通過try{...}catch{...}捕獲異常。
還可以使用多個catch子句來捕獲不同的Exception及其子類。
如:
catch捕獲時可以用或操作符“|”在一條catch子句中同時捕獲多種錯誤。
如:
寫的時候注意catch的順序,子類必須寫前面,因為是按從上到下的順序捕獲的。如果父類在前,由於某個子類也屬於這個父類,那麼就會先被父類的catch給捕獲。
如:
finally語句可以保證有無錯誤都會執行。finally語句不是必須的,finally總是最後才執行。
如:
總結:
catch子句的匹配順序非常重要,子類必須放前面;
finally子句保證有無異常都會執行,finally是可選的;
catch可以匹配多個非繼承關係的異常(JDK>=1.7)。
丟擲異常:
當某個方法丟擲異常時,如果當前方法沒有捕獲,異常就會被拋到上層呼叫方法,直到遇到某個try...catch被捕獲。
如:
printStackTrace()可以打印出方法的呼叫棧,對於除錯錯誤非常有用。
如:
這樣的就是方法呼叫棧。從中我們可以一步一步找出異常最初是從哪裡來的。
觀察呼叫棧,我們可以找出異常是從哪裡被丟擲的。
如何丟擲異常?
建立某個Exception的例項,用throw語句丟擲。
如:
轉換異常:
如果一個方法捕獲了某個異常後,又在catch中丟擲新的異常,就相當於把丟擲的異常型別給“轉換”了。
如:
新的異常會丟失原有的異常資訊,我們可以讓新的Exception持有原始異常資訊。
如:
注意:
在丟擲異常前,finally語句會保證執行。如果finally語句丟擲異常,則catch語句將要丟擲的異常不再執行。
如:
箭頭所指的沒有被丟擲的異常稱為“被遮蔽”的異常(suppressed exception)。
如何儲存所有的異常資訊?
我們可以用origin變數儲存原始異常;如果存在原始異常,用addSuppressed()新增新異常;如果存在原始異常,或者新異常,最後在finally丟擲。
如:
我們可以用getSupperssed()獲取所有的Suppressed Exception。
如:
總結:
printStackTrace()可以列印異常的傳播棧,對除錯很有用;
捕獲異常並再次丟擲新的異常時,應該持有原始異常資訊;
如果在finally中丟擲異常,應該把新丟擲的異常新增到原有異常中;
用getSuppressed()可以獲取所有新增的Supperssed Exception;
處理Suppressed Exception要求JDK>=1.7。
自定義異常:
首先看看JDK已有的異常:
我們還可以定義新的異常型別,新的異常型別可以從合適的Exception派生,推薦從RuntimeException派生,這樣我們就不需要強制捕獲自定義的異常。也不需要在方法中宣告需要丟擲異常。
我們可以定義新的異常關係樹:從適合的Exception派生BaseException,其他Exception從BaseException派生。
如:
自定義的異常應該提供多個構造方法。
總結:
自定義異常應該從合適的Exception派生;
推薦用RuntimeException;
自定義異常應該提供多個構造方法;
可以使用IDE根據父類快速建立構造方法。
Java斷言和日誌:
斷言:
斷言(Assertion)是一種程式除錯的方式,使用assert關鍵字,斷言條件預期為true,如果斷言失敗,丟擲AssertionError。我們還可以設定一個斷言訊息。
如:
斷言的特點:
斷言失敗時會丟擲AssertionError,導致程式結束退出;
不能用於可恢復的程式錯誤(可恢復的程式錯誤不應該使用斷言);
只應該用於開發和測試階段。
JVM預設關閉斷言指令:
給Java虛擬機器傳遞-ea引數啟用斷言;
可以指定特定的類啟用斷言;
可以指定特定的包啟用斷言。
總結:
斷言是除錯方式,斷言失敗會丟擲AssertionError;
只能在開發和測試階段啟用斷言;
對可恢復的錯誤不能使用斷言,應該丟擲異常;
斷言很少被使用,更好地方法是編寫單元測試。
日誌:
日誌(logging)是為了取代System.out.println()語句。
日誌可以設定輸出樣式;可以設定輸出級別,禁止某些級別輸出;可以被重定向到檔案;可以按包名控制日誌級別。
JDK內建了Logging(即JDK Logging),在java.util.logging包中。
如:
JDK Logging定義了7個日誌級別:
如果設定為INFO,則輸出INFO以上的3個級別的日誌。
JDK Logging的侷限:
JVM啟動時讀取配置檔案並完成初始化;
JVM啟動後無法修改配置;
需要在JVM啟動時傳遞引數-Djava.util.logging.config.file=config-file-name。
總結:
日誌是為了替代System.out.println(),可以定義格式,重定向到檔案等;
日誌可以存檔,便於追蹤問題;
日誌記錄可以按級別分類,便於開啟或者關閉某些級別;
可以根據配置檔案調整日誌,無需修改程式碼;
JDK提供了Logging:java.util.logging。
Commons Logging:
Commons Logging是Apache建立的日誌模組:
可以掛接不同的日誌系統;
可以通過配置檔案指定掛接的日誌系統;
自動搜尋並使用Log4j;
使用JDK Logging(JDK>=1.4)。
如:
Commons Logging定義了6個日誌級別:
如何在Eclipse中引入jar包:
Project -> Property -> Java Build Path -> Libraries -> Add Jars...
建議在工程目錄下建立一個lib資料夾,把所有引用的jar包都放到這個資料夾內。
初始化Log物件:
final Log log = LogFactory.getLog(getClass());
總結:
Commons Logging是使用最廣泛的日誌模組;
Commons Logging的API非常簡單;
Commons Logging可以自動使用其他日誌模組。
Log4j:
Log4j是目前最流行的日誌框架。目前已有2.X版本。
Appender可以把同一個log輸出到不同的目的地:Console(螢幕)、File、Socket(遠端);Filter是過濾器,決定哪些log需要被輸出;Layout用來格式化log資訊。
Commons Logging如果在classpath中發現了log4j,就會使用log4j。
最佳使用方法:
使用Commons Logging介面來寫入日誌;
開發階段無需引用Log4j;
使用Log4j只需要把正確的配置檔案和相關jar包放入工程的classpath中;
使用配置檔案可以靈活修改日誌的輸出,無需修改程式碼。
總結:
通過Commons Logging實現日誌,不需要修改程式碼即可使用Log4j;
使用Log4j只需要把log4j2.xml和相關jar放入classpath;
如果要更換Log4j,只需要移除log4j2.xml和相關jar;
只有擴充套件Log4j時,才需要引用Log4j的介面。