1. 程式人生 > >Java核心程式設計十:異常處理與日誌

Java核心程式設計十:異常處理與日誌

1 異常層次 需要考察的異常情況有:使用者輸入錯誤、裝置錯誤、物理限制、程式錯誤,傳統的返回錯誤碼的方法並不能處理所有的情況。 1.1 異常分類
在Java程式中,異常物件都派生於Throwable。如果Java的內建異常不滿足需求,則可以建立自定義異常。下圖是基本的異常體系: Error類描述了Java執行時系統的內部錯誤和資源耗盡錯誤,此類錯誤不應由應用程式丟擲。 而Exception分兩類,一類是因程式邏輯問題,導致系統出問題,此時就是Runtime異常,如陣列越界、型別轉換錯誤、訪問空指標。而如果程式可以正常,但在遇到不同IO情況時,出問題導致,則屬於IO異常,如開啟格式錯誤的URL、在檔案尾部讀資料、載入不存在的類檔案等。 RuntimeException一定是程式寫的有問題,是可以避免出現的。Java將Error和RuntimeException定義為未檢查的異常。其他異常為已檢查。編譯器將檢查程式碼是否為所有已檢查異常提供異常處理器。 1.2 異常宣告
方法應該在首部宣告其所有可能丟擲的異常,例如: public void lookup() throws XXException,YYException 一個方法必須宣告所有可能丟擲的已檢查異常,而未檢查異常要麼不可控,要麼就應該避免發生。如果方法沒有宣告所有可能發生的已檢查異常,編譯器就會給出錯誤訊息。 當程式呼叫一個丟擲已檢查異常的方法、或程式自己會丟擲異常時,有必要進行異常宣告,對於Error和Runtime這些未檢查異常,無人可以預料,因此不需要宣告。 1.3 自定義異常 如果遇到標準異常不能說明的情況,則需要自定義異常。只需要繼承自某個異常類,並定義兩個建構函式即可。一個預設建構函式,一個帶有描述資訊的構造器。 2 捕獲異常
異常的捕獲需要周密的計劃。如果異常沒有被捕獲,程式會終止,並會在控制檯上輸出異常資訊。對UI程式,異常出現後會提示使用者,使用者可以繼續返回到程式中。 使用try塊來捕獲異常 try { } catch(XXXException e) { } catch(YYYException e) { } 如果try語句中的任何程式碼丟擲一個在catch中指定的異常,則程式會跳過try語句中的其餘程式碼轉而執行catch子句中的程式碼。如果丟擲的是不在catch中指定的異常,則程式立即返回到上層呼叫者。 當捕獲到異常後,一種方法是我們自己處理;另一種是不處理,交給呼叫者去處理,這樣只需要宣告異常即可。如果應該捕獲那些知道如何處理的異常,而將不知道如何處理的異常傳遞給上層。將異常交給足以勝任的處理器,比壓抑它好得多。 2.1 捕獲多個異常
通過示例程式碼,可以捕獲多個異常 2.2 鏈式異常 當捕獲到異常後,我們可以改變其型別,並重新丟擲。這樣做可以隱藏底層異常的細節,保持異常解釋的抽象性。為了獲得真正的底層異常,可以用setCause()方法將底層異常包裝到高層異常中,並在需要的時候用getCause()來獲取。 2.3 finally子句 不論異常是否發生,finally中的語句都會被執行,這可以作為一種比較合適的清理資源的方式。當然如果在finally中丟擲異常,則會導致try中異常型別丟失。因此建議異常的使用風格如下: try {     try     {     }
    finally
    {     } } catch() { } 內層的try..finally負責清理資源,而外層的try只負責報告錯誤。 2.4 堆疊跟蹤 java.lang.Throwable Throwable(String s) Throwable(Throwable cause)  Throwable(String s,Throwable cause)  用給定的cause構造一個異常 Throwable getCause() void setCause() String getMessage() 獲得描述資訊 StackTraceElement[]  getStackTrace() 獲取構造這個物件時呼叫堆疊的跟蹤 java.lang.Exception Exception(Throwable cause)  Exception(String s,Throwable cause)  用給定的cause構造一個異常 java.lang.RuntimeException RuntimeException(Throwable cause)  RuntimeException(String s,Throwable cause)  用給定的cause構造一個異常 java.lang.StackTraceElement String getFileName() 返回元素執行時對應的原始檔名 int getLineNumber()  返回元素執行時對應的原始檔行數 String getClassName() 返回元素執行時類命名 String getMethodName() 返回方法名 bool isnativeMethod() 2.5 異常機制使用建議 I 異常機制對於效能損耗較大,最好使用簡單測試來避免異常發生,如在stack非空時才呼叫pop,比使用try包圍的pop效能高100倍 II 不要過分細分異常,將一段正常的流程放在一個try塊中而不是分到不同的try塊中,有得於程式碼的閱讀。 III 善於利用異常層次,不要只丟擲RuntimeException,而應該尋找和建立更適合的子類;也不要只捕獲Throwable,否則程式碼會較難閱讀。 IV 合理壓制異常。如果認為異常不是問題則可以合理的壓制,否則則應該宣告或丟擲。 V 早丟擲,晚捕獲;在程式出錯的地方立即丟擲異常,而儘可能向高層傳遞非細節異常。 3 記錄日誌 3.1 logging包的類結構
3.2 全域性日誌 日誌模組,擁有一個全域性變數Logger.global,可以用它向控制檯輸出資訊。 Logger.global.info(String s)  列印日誌到標準輸出 Logger.global.setLevel(Level level)  設定日誌級別 3.3 命名日誌 日誌也具有層次結構,不同的包可以按結構名來獲取各自的日誌,把記錄器命名與主程式包一樣的名字是一個好的實踐。 日誌有7個級別:SEVERE WARNING INFO CONFIG FINE FINER FINEST,預設為INFO級別。ALL代表所有級別,OFF表示關閉。 java.util.logging.Logger類 static Logger getLogger(String path.name.log)  static Logger getLogger(String name,String resName) 提供資源包的日誌 void setLevel(Level l) 設定日誌記錄器級別 Level getLevel() void addHandler(Handler h)  新增處理器 void removeHandler(handler h) void setUseParantHandlers(bool flag) 設定是否使用父處理器 void log(Level level,String msg)  按指定級別記錄日誌 void log(Level level,String format,Object[] objs)  按指定級別記錄日誌 void logp(Level level,String classname,String methodName,String msg) void logp(Level level,String classname,String methodName,String format,Object[] objs) void logrb(Level level,String classname,String methodName,String resname,String format,Object[] objs) void throwing(String classname,String methodName,Thowable t) 記錄異常的資訊 3.4 日誌管理器配置 日誌系統的配置預設位於jre/lib/logging.properties中。 如果要使用不同的配置,要在JVM啟動時指定,java -Djava.util.logging.config.file = path MainClass 在配置檔案中可以指定日誌處理器、對日誌處理器進行相應的配置、日誌輸出級別、日誌輸出格式等 3.5 日誌的國際化 在獲取日誌時,還可以為日誌指定資源包,以支援本地化的日誌顯示。 3.6 日誌處理器 預設情況下,日誌將記錄傳送到ConsoleHandler中,並由它輸出到System.err流中。日誌處理器也有級別,其只處理日誌條目級別高於處理器級別的條目。 如果想繞過配置,則需要自己設定日誌記錄器的級別和處理器的級別,並將處理器安裝到日誌上。 如果希望將日誌傳送到其他地方,需要新增其他的處理器。系統提供了兩個有用的處理器,FileHandler和Sockethandler。
使用者可以自行繼承自Handler或StreamHandler來實現自定義的日誌輸出方式。
java.util.logging.handler abstract void publish(LogRecord lr) 將日誌寫到特定流 abstract void flush() 重新整理已緩衝資料 Formatter getFormatter() void setFormatter(Formatter f)  設定格式化器 void setLevel(Level l) 設定處理級別 java.util.logging.ConsoleHandler java.util.logging.FileHandler FileHandler(String namePatter,bool append)  以日誌命名模式和是否追加初始化日誌 FileHandler(String namePatter,int limit,int count,int append)  limit控制日誌內最大條數,count控制迴圈日誌數 3.7 格式化器 系統提供的處理器可以生成平坦文字和XML格式的日誌記錄,我們也可以自定義格式,只需要擴充套件Formatter類並覆蓋format()方法即可。對於XML格式化,需要覆蓋getHead/Tail來提供特定的頭和尾內容。 java.util.logging.Formatter abstract String format(LogRecord lr) String getHead/Tail() String formatMessage(LogRecord lr) 4 使用斷言 assert cond       如果cond為假,則丟擲異常 assert cond:expr  如果cond為假,則使用expr作字串構造異常 在預設時,斷言並不生效,通過在控制檯增加enable進行啟用:java -enableassertion myapp 或java -ea:class1 -ea:packet1 啟用和禁用斷言並不需要重新編譯程式,這是由類載入器來完成的。對於由虛擬機器載入的類,可以使用esa來進行載入。 斷言是一種在開發和測試中使用的技術,不要將斷言代替異常和日誌。 5 除錯技術 5.1 常用方法 5.1.1 使用print或log方法輸出程式資訊到螢幕或檔案中 5.1.2 在每個類中放置main方法,進行單元測試 5.1.3 使用JUnit進行單元測試 5.1.4 使用throwable提供的printStackTrace或thread.dumpStack跟蹤執行堆疊 5.1.5 使用 -Xprof選項執行虛擬機器,跟蹤程式熱點 5.2 圖形程式測試 awt提供了Robot類,可以設定鍵盤和滑鼠操作,並對UI進行測試。 5.3 使用偵錯程式 5.3.1 JDB 一個類似於GDB的工具,可以對Java程式進行除錯,不推薦使用。 5.3.2 Eclipse偵錯程式 不在此描述