1. 程式人生 > >Java錯誤處理、Java斷言和日誌

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的介面。