標籤:資訊 with animation enable checked 學習 time iter super
第11章 異常,斷言,日誌,調試
- 處理錯誤
- 捕獲異常
- 使用異常機制的技巧
- 使用斷言
- 日誌
- 測試技巧
- GUI程式排錯技巧
- 使用調試器
11.1 處理錯誤11.1.1異常分類
11.1.2 聲明已檢查異常
下面四種情況自己編寫方法時。須要拋出異常
- 調用一個拋出checked異常的方法。比如,FileInputStream構造器
- 程式運行發現錯誤,而且利用throw語句拋出一個checked異常
- Java虛擬機器和執行階段程式庫出現的內部錯誤
對於可能被他人使用的Java方法,更具異常規範(exception specification),在方法的首部聲明這種方法可能拋出異常,假設有多個用逗號隔開
class MyAnimation{ ... public Image loadImage(String s) throws IOException FileNotFoundException { ... }}
關於子類覆蓋超類的方法那一塊沒看懂
11.1.3 怎樣拋出異常
- 找到一個合適的異常類
- 在方法聲明
- 建立這個類的一個對象
將對象拋出
String readData(Scanner in) thros EOFException{ ... while(...) { if(!in.hasNext()) { if(n<len) throw new EOFException(); } } ... return S;}//還能含有一個字串參數的構造器String girpe="Content-length " +len +",Recived" + n ;throw new EOFException(girpe);
11.1.4 建立異常類
建立一個派生於Exception的類 ,或者派生於Exception子類的類。
一般須要定義兩個構造器,一個是預設的,另一個是帶有具體描寫敘述資訊的構造器(超類Throwable的toString方法將會列印這些具體資訊)
class FileFormatException extends IOException{ public FileFormatException() {} public FileFormatException(String gripe) { super(gripe); }}
String getMessage()
能獲得Throwable對象具體描寫敘述資訊,即構造時丟進去的String。
11.2 捕獲異常
- 假設異常沒有被捕獲,程式將會停止運行
假設想捕獲異常,下面是最簡單的try/catch 語句塊
try{ code more code more code}catch (ExceptionType e){ handler for this type }
假設try語句塊中的不論什麼代碼拋出了在catch子句中說明的異常。那麼
- 跳過剩下的try 語句
- 將運行catch子句中的處理器代碼
下面是個簡單的範例
public void read(String filename){ try { InputStrem in = new FileInputStream(filename); int b; while((b!=in.read())!=-1) { process input; } } catch (IOException exception) { exception.printStackTrace(); }}
對於以上代碼
通常最好的選擇是什麼都不做,而是將異常傳遞給調用者。
假設read方法出現了錯誤,就讓read方法的調用者去擔心!
假設採用這樣的處理方式,就必須聲明這種方法可能會拋出一個IOException
public void read(String filename) throws IOException{ InputStrem in = new FileInputStream(filename); int b; while((b!=in.read())!=-1) { process input; }}
- 不同意子類覆蓋超類的方法中throws說明符超過超類所列出的異常範圍。假設有不屬於的,不能throws掉,僅僅能自己捕獲處理
11.2.1 捕獲多個異常
基本語句
try {}catch (FileNotFoundException e){}catch (UnknownHostException e){}catch (IOException e){}
假設須要獲得具體資訊
e.getMessage() //具體錯誤資訊e.getClass().getName() //異常對象的實際類型
假設處理方式一樣 合并catch語句
try{ code..}catch (FileNotFoundException | UnknownHostException e){}
11.2.2 再次拋出異常或異常鏈
捕獲異常再次拋出的基本方法
try{ access the database}catch (SQLException e){ throw new ServletException("database error: "+e.getMessage());}
另一種能不丟失原始異常的方法
try{ access the database}catch (SQLException e){ Throwable se=new ServletException("database error"); se.initCause(e); throw se;}
當捕獲異常時。能夠用下面語句又一次得到原始異常:
Throwable e = se.getCause();
書上強烈建議這樣的封裝方式,不丟失原異常的細節。
有時僅僅是記錄異常
try{ access the database}catch (SQLException e){ logger.log(level,message,e); throw e;}
11.2.3 finaly子句
基本的文法
try{}catch(Exception e){}finally{ in.close();}
不管try中拋出異常,還是catch中拋出異常,總而言之finall總會運行
強烈使用try/catch 和 try/finally語句塊單獨,而不是基本的文法的使用方法。例如以下:
InputStream in= ... ;try { try { code that might throw exceptions } finally { in.close(); }}catch(IOException e){ show error message}
內層的try僅僅有一個職責,確認關閉輸入資料流。外層的try 用來確保報告出現的錯誤。
注意:當finally 和catch包括return語句,會運行finally的return。
注意:finally 也可能拋出異常,而導致本來要catch拋出的異常被覆蓋
11.2.4 帶資源的try語句
假如資源實現了AutoCloseable/Closeable介面的類。能夠利用帶資源的try語句
try(Resource res=...){ work with res}
try 退出時,會自己主動調用res.close()。下面給出一個典型的範例。
try (Scanner in=new Scanner(new FileInputStream("/usr/share/dict/words"))){ while (in.hasNext()) System.out.println(in.next());}
還能指定多個資源,比如:
try (Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words")), PrintWriter out=new PrintWriter("out.txt")){ while(in.hasNext()) out.println(in.next().toUpperCase());}
假設.close()也拋出了異常,可是不會覆蓋原來該拋出的異常,而是被抑制。假設你想知道這些被抑制的異常,能夠通過getSuppressed方法。
11.2.5 分析堆疊追蹤元素
堆疊追蹤(stack trace)是一個方法調用過程的列表。
比較靈活的方式是使用getStackTrace()。
它會得到StackTraceElement對象的一個數組。
比如:
Throwable t = new Throwable();StackTraceElement[] frames = t.getStackTrace();for(StackTraceElement f : frames) System.out.println(f); 輸出factorial(4):StackTraceTest.factorial(StackTraceTest.java:8)StackTraceTest.factorial(StackTraceTest.java:14)StackTraceTest.factorial(StackTraceTest.java:14)StackTraceTest.factorial(StackTraceTest.java:14)StackTraceTest.factorial(StackTraceTest.java:14)StackTraceTest.factorial(StackTrcaceTest.java:14)StackTraceTest.main(StackTraceTest.java:23)
能獲得檔案名稱,類名。當前啟動並執行程式碼號
靜態Thread.getAllStackTrace方法,獲得全部線程的堆疊追蹤。下面是範例
Map<Thread,StackTraceElement[]> map=Thread.getAllStackTraces();for(Thread t : map.keySet()){ StackTraceElement[] frames=map.get(t); for(StackTraceElement f : frames) System.out.println(f);}
11.3 使用異常機制的技巧
- 異常不能取代簡單的測試
- 不要過分細化異常
- 利用異常階層
- 不要羞於傳遞異常。有時候你是類設計者,應該由使用者決定對異常怎麼樣
11.4 使用斷言
11.4.1 啟用或者禁用斷言
- -ea或 -enableassertions啟用,預設是禁用
- 也能啟動部分包的斷言,也能金庸部分包的斷言
11.4.2 使用斷言完畢參數檢查
- 宣告失敗是致命的,不可恢複的錯誤。
- 斷言檢查僅僅用於開發和測試階段。
- 斷言是一種測試和調試階段所使用的戰術性工具,而日誌記錄是一種在程式的整個生命週期都能夠使用的策略工具。
11.5 記錄日誌11.5.1 基本日誌
日誌系統管理著一個名為Logger.global的預設日誌記錄器,能夠用System.out替換它,並通過info方法記錄日誌資訊
Logger.getGlobal().info("File->Open menu item selected");//print //三月 15, 2016 7:33:25 下午 log main //資訊: File->Open menu item selected
自己主動包括了時間,調用的類名和方法。
Logger.gelGlobal().setLevel(Level.OFF)來取消全部日誌
11.5.2 進階日誌
調用getLogger方法能夠建立或檢索記錄器
Logger myLogger= Logger.getLogger("log.zhouyong");
假設對日誌設定了記錄層級。那麼它的子記錄器也會繼承這個屬性
有一下7個日誌記錄器層級
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
在預設情況。僅僅記錄前三個層級。也能夠設定其它層級。比如:
logger.setLevel(Level.FINE)
如今,FINE和更進階別的記錄都能夠記錄下來
另外能夠使用Level.ALL 開啟全部 Level.OFF 關閉全部
有下面幾種記錄方式
logger.warning(message);logger.fine(message);//同一時候還能夠用log方法指定層級logger.log(Level.FINE,message);
一般用CONFIG,FINE等記錄有助於診斷,但對於程式猿沒有太大意義的調試資訊。
預設的日誌記錄將顯示包括日誌調用類名和方法名。可是假設虛擬機器進行了最佳化,可能無法得到準確的資訊。此時須要logp方法獲得調用類和方法的確切位置,簽名例如以下:
void logp(level l,String className,String methodName,String message)
下面另一些跟蹤運行流的方法
void entering(String className,String methodName)void entering(String className,String methodName,Object param)void entering(String className,String methodName,Object[] params)void exiting(String className,String methodName)void exiting(String className,String methodName,Object result)
比如:
不知道有什麼用。。
記錄日誌的經常使用用途是記錄那些不可預料的異常。能夠使用一下兩種方式。
void throwing(String className,String methodName,Throwable t)void log(Level l,String message ,Throwable t)
典型的使用方法是:
if(...){ IOExcption exception = new IOException("..."); logger.throwing("com.my","read",exception); throw exception; //FINER層級}
還有
try{ ...}catch (IOException e){ Logger.getLogger("...").log(Level.WARNING,"Reading image",e);}
11.5.3 改動日誌管理器配置
預設情況,設定檔存在於:
e/lib/logging.properties
要想使用自己的設定檔 須要
java -Djava.util.logging.config.file=configFile MainClass
改動預設的日誌記錄層級
.level=INFO
還能改動自己日誌記錄層級
com.mycompany.myapp.level=FINE
控制台也有輸出層級限制
java.util.logging.ConsoleHandler.level=FINE
日誌屬性由java.util.logging.LogManager類處理。具體看API
11.5.4 本地化
11.5.5 處理器
日誌記錄器先會將記錄發送到父處理器中,終於的處理器有一個ConsoleHandle。
對於一個要被記錄的日誌記錄,它的日誌記錄層級必須高於日誌記錄器和處理器的閾值。
要想記錄FINE層級的日誌。就必須改動設定檔裡的預設日誌記錄層級和處理器層級。
另外,還能夠繞過設定檔,安裝自己的處理器。
//控制台處理器
Logger logger=Logger.getLogger("log.zhouyong");logger.setLevel(Level.FINE);logger.setUseParentHandlers(false);Handler handler = new ConsoleHandler();handler.setLevel(Level.FINE);logger.addHandler(handler);logger.log(Level.FINE,"dddd");
在預設情況,日誌記錄器會將記錄發送給自己的處理器和父處理器。
父處理器就是一般的預設處理器,可是既然我們有了自己的處理器,能夠把父處理器關了。免得控制台發送了兩次記錄
要想將日誌發送到別的地方,就須要其它處理器。
- FileHandler 收集檔案裡的日誌
- SocketHandler。 發送到特定的主機和port
FileHandler
FileHandler handler = new FileHandler();handler.setLevel(Level.FINE);logger.addHandler(handler);logger.log(Level.FINE,"dddd");
檔案在User底下。格式為XML。例如以下:
<?xml version="1.0" encoding="GBK" standalone="no"?><!DOCTYPE log SYSTEM "logger.dtd"><log><record> <date>2016-03-15T23:40:17</date> <millis>1458056417956</millis> <sequence>0</sequence> <logger>log.zhouyong</logger> <level>FINE</level> <class>log</class> <method>main</method> <thread>1</thread> <message>dddd</message></record></log>
- 還能有很多其它的複雜方式來處理完畢自己想要的要求
11.5.6 過濾器
11.5.7 格式化器
ConsoleHandler類和FileHandler能夠產生文本或 XML格式的日誌記錄。可是也能夠自己定義格式。
通過繼承Formatter類,並覆蓋一下方法。
String format(LogRecord record)
能夠依據自己意願對記錄的資訊進行格式化,並返回結果字串。
然後用setFormatter方法將格式化器安裝到處理器中。
11.5.8 日誌記錄說明
[core java學習筆記][第十一章異常斷言日誌調試]