異常
1、對於異常情況,Java使用一種稱爲異常處理(exception handing)的錯誤捕獲機制處理。
2、用戶期望在出現錯誤是,程序能夠採用一些理智的行爲,如果由於出現錯誤而使得某些操作沒有完成,程序應該:
·返回到一種安全的狀態並能夠讓用戶執行一些其他的命令
·允許用戶保存所有操作的結果,並以適當的方式終止程序。
3、在Java中,如果某個方法不能夠採用正常的途徑完成它的任務,就可以通過另外一個路徑退出方法。在這種情況下,方法並不返回任何值,而是拋出(throw)一個封裝了錯誤信息的對象。
4、在Java程序設計語言中,異常對象都是派生與Throwable類的一個實例。Java異常層次結構如下:
5、Error類層次結構描述了Java運行時系統的內部錯誤和資源耗盡錯誤。應用程序不應該拋出這種類型的對象。
6、在設計Java程序時,需要關注Exception層次結構。這個層次結構有分解爲兩個分支:一分支派生與RuntimeException;另一分支包含其他異常。劃分兩個分支的規則是,由程序錯誤導致的異常屬於RuntimeException;而程序本身沒有問題,但由於像I/O錯誤這類問題導致的異常屬於其他異常。
7、派生於RuntimeException的異常包含下面幾種情況:
·錯誤的類型轉換
·數據訪問越界
·訪問空指針。
不是派生於RuntimeException的異常包括:
·試圖在文件尾部後面讀取數據
·試圖打開一個不存在的文件。
·試圖根據給定的字符串查找Class對象,而這個字符串表示的類不存在。
8、“如果出現RuntimeException異常,那麼就一定是你的問題”。
9、Java語言規範將派生於Error類和RuntimeException類的所有異常稱爲未檢查(unchecked)異常,所有其他的異常稱爲已檢查(checked)異常。
10、在遇到下面4中情況時應該拋出異常:
·調用一個拋出已檢查異常的方法。
·程序運行過程中發現錯誤,並且利用throw語句拋出一個已檢查異常
·程序出現錯誤。
·Java虛擬機和運行時庫出現的內部錯誤。
11、對於那些可能被他人使用的Java方法,應該根據異常規範在方法的首部聲明這個方法可能拋出異常。
class myAnimation {
...
public Image loadImage(String s)
throws IOException {
...
}
}
12、如果一個方法可能拋出多個已檢查異常,那麼就必須在方法的首部列出所有的異常類。每個異常類之間用逗號隔開:
class myAnimation {
...
public Image loadImage(String s)
throws FileNotFoundException, EOFException {
...
}
}
13、但是,不需要聲明Java的內部錯誤,即從Error繼承的錯誤。任何程序代碼都有拋出那些異常的潛能,而我們對其沒有任何控制能力。同樣也不應該聲明從RuntimeException集成的那些未檢查的異常。
14、一個方法必須聲明所有可能拋出的已檢查異常,而未檢查異常要麼不可控制,要麼就應該避免發生。
15、如果在子類中覆蓋了一個超類的方法,這類方法中聲明的已檢查異常不能比超類中聲明的異常更通用。
16、如果超類方法中沒有拋出任何已檢查異常,子類不能拋出任何已檢查異常。
17、使用throw關鍵字拋出異常:
throw new EOFException();
18、在程序中可能會原道任何標準異常類都沒有能夠充分地描述清楚的問題,此時需要創建自己的異常類,創建的異常類必須派生與IOException或其子類。
19、習慣上,定義的異常類應該包含2個構造器,一個是默認的構造器,另一個是帶有詳細描述信息的構造器。
20、要想捕獲一個異常,必須設置try/catch語句塊,格式如下:
try {
code
more code
...
} catch (ExceptionType e) {
handler for this type;
}
21、如果try語句塊中的任何代碼拋出了一個在catch字句中說明的異常類,那麼:
① 程序將跳過try語句塊的其餘代碼。
② 程序將執行catch字句中的處理器代碼。
22、編譯器將嚴格地執行throws說明符。如果調用了一個拋出一個已檢查異常的方法,就必須對它進行處理,或者將它繼續進行傳遞。
23、通常,應該捕獲那些知道如何處理的異常,而將那些不知道怎樣處理的異常繼續進行傳遞。
24、如果編寫一個覆蓋超類的方法,而這個方法有沒有拋出異常,那麼這個方法就必須捕獲方法代碼中出現的每一個已檢查異常。不允許再來在throws方法說明符中出現超過超類方法所列出的異常類範圍。
25、在一個try語句塊中可以不過多個異常類型,並對不同類型的異常做出不同的處理。可以按照下列方式爲每個異常類型使用一個單獨的catch子句:
try {
code that might throw exceptions;
...
] catch (FileNotFoundException e) {
emergency action for mising files.
} catch (UnknownHostException e) {
emergency action for unknown hosts;
} catch (IOException e) {
emergency action for all other I/O problems.
}
26、異常對象可能包含與異常本省有關的信息。想要獲得對象的更多信息,可以試着使用e.getMessage(),或者使用e.getClass().getName()獲得異常對象的實際類型。
27、在JavaSE 7 中,同一個catch子句可以捕獲多個異常類型:
try {
code that might throw exceptions;
...
] catch (FileNotFoundException | UnknownHostException e) {
emergency action for mising files.
} catch (IOException e) {
emergency action for all other I/O problems.
}
只有當捕獲異常類型彼此之間不存在子類關係時才需要這個特性。
28、可以在catch語句中再拋出一個異常,這樣做的目的是改變異常的類型。
29、可以使用一種“包裝”技術使得用戶可以在拋出子系統中的高級異常,同時不丟失原始異常的細節:
try {
access database
} catch (SQLException e) {
Throwable se = new ServletException(“database error”);
se.initCause(e);
throw se;
}
如果一個方法中發生了一個已檢查異常,而不允許拋出它,那麼包裝技術就十分有用,我們可以捕獲這個已檢查異常,並將它包裝成一個運行時異常。
30、不管是否有異常被捕獲,finally字句中的代碼都會被執行。
31、try語句可以只有finally子句,而沒有catch子句。
32、建議獨立使用try/catch和try/finally語句塊,這樣可以提高代碼的清晰度:
try {
try {
code that might throw exceptions;
} finally {
in.close();
}
} catch (IOException e) {
show error message;
}
這樣的設計方式不僅清楚,而且還具有一個功能,就是將會報告finally子句中出現的錯誤。
33、當finally字句中包含return語句時,將會出現一種意想不到的結果,因爲在方法返回前,finally字句的內容將被執行,如果finally字句中也有一個return語句,這個返回值會覆蓋原始的返回值。
34、如果finally語句塊中的代碼拋出了異常,這將會覆蓋原始的異常。如要拋出原來的異常,代碼會變得極其繁瑣:
Exception ex = null;
try {
try {
code that might throw exceptions;
} catch (Exception e) {
ex = e;
throw e;
}
} finally {
try {
in.close();
} catch (exception e) {
if(ex == null) throw e;
}
}
35、對於實現了AutoCloseable接口的資源類,Java SE 7 位其提供了一個很有用的快捷方式:
try (Resource res = ...) {
work with res;
}
try塊退出時,會自動調用res.close().
36、調用Throwable類中的printStackTrace方法可以查看堆棧跟蹤的文本描述信息:
Throwable t = new Throwable();
ByteArrayOutputStream out = new ByteArrayOutputStream();
t.printStackTrace(out);
String description = out.toString();
37、一種更靈活的方法是使用getStackTrace()方法,它會得到StackTraceElement對象的一個數組,可以在程序中分析:
Throwable t = new Throwable();
StackTraceElement[] frames = t.getStackTrace();
for(StackTrace element frame : frames) {
analyze frame;
}
38、常用方法:
java.lang.Throwable 1.0
·Throwable(Throwable cause) 1.4
·Throwable(String message, Throwable cause) 1.4
用給定的“原因”構造一個Throwable對象。
·Throwable initCause(Throwable cause) 1.4
將這個對象設置爲“原因”如果這個對象已經被設置爲“原因”,則拋出一個異常,返回this引用。
·Throwable getCause() 1.4
獲得這個對象的“原因”異常對象。如果沒有設置原因,則返回null。
·StackTraceElement[] getStackTrace() 1.4
獲得構造這個對象時調用的堆棧跟蹤。
·void addSuppressed(Throwable t) 7
爲這個異常增加一個“抑制”異常。這出現在帶子源的try語句中,其中t是close方法拋出的一個異常。
·Throwable[] getSuppressed() 7
得到這個異常的所有“抑制”異常。一般來說,這些是帶資源的try語句中close方法拋出的異常。
java.lang.Exception 1.0
·Exception(Throwable cause) 1.4
·Exception(String message, Throwable cause)
用給定的“原因”構造一個異常對象。
java.lang.RuntimeException 1.0
·RuntimeException(Throwable cause) 1.4
·RuntimeException(String message, Throwable cause) 1.4
用給定的原因,構造一個RuntimeException對象。
java.lang.StackTraceElement 1.4
·String getFileName()
返回這個元素運行時對應的源文件名,如果這個信息不存在,則返回null。
·String getLineNumber()
返回這個元素運行時對應的源文件行數,如果這個信息不存在,則返回-1。
·String getClassName()
返回這個元素運行時對應的類的全名。
·String getMethodName()
返回這個元素運行時對應的方法名。構造器名是<int>;靜態初始化名是<clinit>。這裏無法區分同名的重載方法。
·boolean isNativeMethod()
如果這個元素運行時在一個本地方法中,則返回true。
·String toString()
如果存在的話,返回一個包含類名、方法名、文件名和行數的格式化字符串。
39、使用異常機制的技巧:
① 異常處理不能代替簡單的測試。(只在異常情況下使用異常機制)
② 不要過分地細化異常。
③ 利用異常層次結構
④ 不要壓制異常(強烈地傾向關閉異常)
public Image loadImage(String s) {
try {
code that threatens to throw checked exceptions;
} catch (Exception e) {}
}
⑤ 在檢測錯誤時,“苛刻”要比放任更好。
⑥ 不要羞於傳遞異常。(早拋出,晚捕獲)
斷言
1、斷言機制允許在測試期間向代碼中插入一些檢查語句。當代碼發佈時,這些出入的檢測語句會被自動地移走。
2、使用assert關鍵字使用斷言,格式如下:
assert <條件>
或:
assert <條件> <表達式>
這兩種形式都會對條件進行檢測,如果結果爲false,則拋出一個AssertionError異常,在第二種形式中,表達式將被傳入AssertionError的構造器,並轉換成一個消息字符串。
3、默認情況下,斷言被禁用。可以在運行程序時用-enableassertions或-ea選項啓用它:
java -enableassertions MyApp
4、也可以在某各類或某個包中使用斷言:
java -ea:MyClass -ea:com.mycompany.mylib... MyApp
5、使用-dissableassertions或-da選項禁用斷言。
6、使用斷言的時機:
·斷言失敗是致命的、不可恢復的錯誤。
·斷言檢查只用於開發和測試階段(這種做法有時候被戲稱爲“在靠近海岸時穿上救生衣,但在海中央時就把救生衣拋掉吧”)
7、常用方法:
·void setDefaultAssertionStatus(boolean b) 1.4
對於通過類加載器加載的所有類來說,如果沒有顯式地說明類或包的斷言狀態,就啓用或禁用斷言。
·void setClassAssertionSatus(String className, boolean b) 1.4
對於給定的類和它的內部類,啓用或禁用斷言。
·void setPackageAssertionStatus(String packageName, boolean b) 1.4
對於給定包和其子包中的所有類,啓用或禁用斷言。
·void clearAssertionStatus() 1.4
移去所有類和包的顯式斷言設置,並金童所有通過這個類加載器加載的類的斷言。
日誌
1、日誌記錄API的優點:
·可以很容易地取消全部日誌記錄,或者僅僅取消某個級別的日誌,而且打開和關閉這個操作也很容易。
·可以簡單地禁止日誌記錄的輸出,因此,將這些日誌代碼留在程序中的開銷很小。
·日誌記錄可以被定向到不同的處理器,用於在控制檯總顯示,用於存儲在文件中等。
·日誌記錄器和處理器都可以對記錄進行過濾。過濾器可以根據過濾實現器指定的標準丟棄那些無用的記錄項。
·日誌記錄可以採用不同的方式格式化,例如純文本或XML。
·應用程序可以使用多個日誌記錄器,它們使用類似包名這種具有層次結構的名字,例如:com.mycompany.myapp
·在默認情況下,日誌系統的配置由配置文件控制。如果需要的話,應用程序可以替換這個配置。
2、日誌系統管理着一個名爲Logger.global的默認日誌記錄器,可以用System.out來替換它,並通過info方法記錄日誌信息。
Logger.getGlobal().info(“File->Open menu item selected”);
3、在專業的應用程序中,不要講所有的日誌記錄到一個全局日誌記錄器中,應該自定義日誌記錄器。
private static final Logger myLogger =
Logger.getLogger(“com.mycompany.myapp);
4、7個日誌記錄級別:
·SEVERE
·WARNING
·INFO
·CONFIG
·FINE
·FINER
·FINEST
默認情況下,只記錄前三個級別。也可以設置其它的級別,例如:
logger.setLevel(Level.FINE);
或者使用Level.ALL開啓所有級別的記錄,很實用Level.OFF關閉所有級別的記錄。
5、跟蹤執行流的方法:
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);
這些調用將生成FINER級別和以字符串“ENTRY”和“RETURN”開始的日誌記錄。
6、提供日誌記錄中包含的異常描述的方法:
void throwing(String className, String methodName, Throwable t);
void log(Level l, String message, Throwable t);
7、可以通過編輯配置文件來修改日誌系統的各種屬性。在默認情況下,配置文件存在於:
e/lib/logging.properties
要想使用另一個配置文件,就要將java.util.logging.config.file特性設置爲配置文件的存儲日誌,並採用下面的命令啓動應用程序:
java -Djava.util.config.file=configFile MainClass
8、要想修改默認的日誌記錄級別,就需要編輯配置文件,並修改一下命令行:
.level=INFO
可以通過添加一下內容來指定自己的日誌記錄級別:
com.mycompany.myapp.level=FINE
也就是說,在日誌記錄後面添加後綴.level
9、要想在控制檯上看到FINE級別的消息,就需要進行下列設置:
java.util.logging.ConsoleHandler.level=FINE
10、在請求日誌記錄器時,可以指定一個資源包:
Logger logger =
Logger.getLogger(loggerName, “com.mycampany.logmessages);
11、在默認情況下,日誌記錄器將記錄發送到ConsoleHandler中,並由它輸出到System.err流中。被別是,日誌記錄器還會將記錄發送到父處理器中.
12、與日誌記錄器一樣,處理器也有日誌記錄級別。對於一個包被記錄的日誌記錄,它的日誌記錄級別必須高於日誌記錄器和處理器的閾值。日誌管理器配置文件設置的默認控制檯處理器的日誌記錄級別爲:
java.util.logging.ConsoleHandler.level=INFO
13、可以繞過配置文件,安裝自己的處理器:
Logger logger = Logger.getLogger(“com.mycompany.myapp”);
logger.setLevel(Level.FINE);
logger.setUseParentHandler(false);
Handler handler = new ConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);
14、日誌API提供了兩個很有用的處理器,一個是FileHandler;另一個是SocketHandler可以將記錄分別發送到文件以及特定的主機端口。
15、直接將記錄發送到默認的文件處理器:
FileHandler handler = new FileHandler();
logger.addHandler(handler);
16、如果多個應用程序使用同一個日誌文件,就應該開啓append標誌。另外,應該在文件名模式中使用%u,以便每個應用程序創建日誌的唯一副本。
17、開啓文件循環功能也是一個不錯的主意。日誌記錄文件以myapp.log.0,myapp.log.1,myapp.log.2,這種循環的形式出現。只要文件大小超出了限制,醉酒的文件就會被刪除,其他的文件將重新命名,同時創建一個新文件,其編號爲0。
18、文件處理器配置參數:
配置屬性 | 描 述 | 默 認 |
java.util.logging.FileHandler.level | 處理器級別 | Level.ALL |
java.util.logging.FileHandler.append | 控制處理器應該追加到一個已經存在的文件的尾部;還是應該爲每個運行的程序打開一個新文件 | false |
java.util.logging.FileHandler.limit | 在打開另一個文件之前允許寫入一個文件的近似最大字節數(0表示無限制) | |
java.util.logging.FileHandler.pattern | 日誌文件名的模式 | %hjava%u.log |
java.util.logging.FileHandler.count | 在循環序列中的日誌記錄數量 | 1(不循環) |
java.util.logging.FileHandler.filter | 使用的過濾器類 | 沒有使用過濾器 |
java.util.logging.FileHandler.encoding | 使用字符編碼 | 平臺的編碼 |
java.util.logging.FileHandler.formatter | 記錄格式器 | java.util.logging.XMLFormatter |
19、日誌記錄文件模式變量:
變 量 |
描 述 |
%h |
系統屬性user.home的值。 |
%t |
系統臨時目錄。 |
%u |
用於解決衝突的唯一性編號。 |
%g |
爲循環日誌記錄生成的數值。(當使用循環功能且模式不包括%g時,使用後綴%g)。 |
%% |
%字符。 |
20、可以通過擴展Handler類或StreamHandler類來自定義處理器。
21、在默認情況下,過濾器根據日誌記錄的級別進行過濾。每個日誌記錄器和處理器都可以有一個可選的過濾器來完成附加的過濾。另外,可以通過實現Filter接口並定義下列方法來自定義過濾器:
boolean isLoggable(LogRecord record);
22、使用setFilter方法將一個過濾器安裝到日誌記錄器或處理器中。
23、ConsoleHandler和FileHandler類可以生成文本和XML格式的日誌記錄。但是也可以自定義格式。這需要擴展Formatter類並覆蓋下面的這個方法:
String format(LogRecord record)
24、日誌記錄基本操作:
① 爲一個簡單的應用程序,選擇一個日誌記錄器,並把日誌記錄器命名爲與主應用程序包一樣的名字,例如:com.mycompany.myprog,這是一種很好的編程習慣,另外可以通過下列方法得到日誌記錄器。
Logger logger = Logger.getLogger(“com.mycompany.myprog”);
爲了方便起見,可能希望利用一些日誌操作量下面的靜態域添加到類中:
private static final Logger logger =
logger.getLogger(“com.mycompany.myporg”);
② 默認的日誌配置將級別等於或高於INFO級別的所有校級記錄到控制檯。用戶可以覆蓋默認的配置文件。但是正如前面所述、改變配置需要做相當多的工作。因此,最好在應用程序中安裝一個更加適宜的默認配置。
③ 現在,可以記錄自己想要的內容了。但需要牢記:所有級別爲INFO、WARNING和SEVERE的消息都將顯示到控制檯上。因此,最好只將對程序用戶有意義的消息設置爲這幾個級別。將程序員想要的日誌記錄,設定爲FINE是一個很好的選擇。
25、常用方法:
java.util.logging.Logger 1.4:
·Logger getLogger(String loggerName);
·Logger getLogger(String loggerName, String bundleName);
·void severe(String message);
·void warning(String message);
·void info(String message);
·void config(String message);
·void fine(String message);
·void finer(String message);
·void finest(String message);
記錄一個由方法名和給定消息顯示級別的日誌記錄。
·void entering(String className, String methodName);
·void entering(String className, String methodName,
Object param);
·void entering(String className, string methodName,
Object[] param);
·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 level, String message);
·void log(Level level, String message, Object obj);
·void log(Level level, String message, Object[] objs);
·void log(Level level, String message, Throwable t);
記錄一個給定級別的消息的日誌記錄,其中可以包括對象或者可拋出對象。要想包括對象,消息中必須包括格式化的佔位符{0}、{1}等。
·void logp(Level level, String className, String methodName,
String message);
·void logp(Level level, String className, String methodName,
String message, Object obj);
·void logp(Level level, String className, String methodName,
String message, Object[] objs);
·void logp(Level level, String className, String methodName,
String message, Throwable t);
記錄一個給定級別、準確的調用者信息和消息的日誌記錄,其中包括對象或可拋出對象。
·void logrb(Level level String className, String methodName,
String bundleName, String message);
·void logrb(Level level String className, String methodName,
String bundleName, String message, Object obj);
·void logrb(Level level String className, String methodName,
String bundleName, String message, Object[] objs);
·void logrb(Level level String className, String methodName,
String bundleName, String message, Throwable t);
記錄一個給定級別、準確的調用者信息、資源包名和消息的日誌記錄,其中可以包括對象或可拋出對象。
·Level getLevel();
·void setLevel(Level l);
獲得和設置這個日誌記錄器的級別
·Logger.getParent();
·void setParent(Logger ();
獲得和設置這個日誌記錄器的父日誌記錄器
·Handler[] getHandlers();
獲得這個日誌記錄器的所有處理器
·void addHandler(Handler h);
·void removeHandler(Handler h);
增加或刪除這個日誌記錄器中的一個處理器。
·boolean getUseParentHandlers();
·void setUseParentHandlers(boolean b);
獲得和設置“use parent handler”屬性。如果這個屬性是true, 則日誌記錄器會將全部的日誌記錄發個它的父處理器。
·Filter getFilter();
·Filter setFilter(Filter f);
獲得和設置這個日誌記錄器的過濾器
java.util.logging.Handler 1.4:
·abstract void publish(LogRecord record);
將日誌記錄發送到希望的目的地。
·abstract void flush();
刷新所有以緩衝的數據。
·abstract void close();
刷新所有已緩衝的數據,並釋放所有相關的資源。
·Filter getFilter();
·void setFilter(Filter f);
獲得和設置這個處理器的過濾器
·Formatter getFormatter();
·void setFormatter(Formatter f);
獲得和設置這個處理器的格式化器。
·Level getLevel();
·void setLevel(Level l);
獲得和設置這個處理器的級別。
java.util.lang.ConsoleHandler 1.4:
·ConsoleHandler()
構造一個新的控制檯處理器。
java.util.longging.FileHandler 1.4:
·FileHandler(String pattern);
·FileHandler(String pattern ,boolean append);
·FileHandler(String pattern, int limit, int count);
·FileHandler(String pattern, int limit, int count,
boolean append);
構造一個文件處理器。
參數:
pattern 構造日誌文件名的模式。
limit 在打開一個新的日誌文件之前,日誌文件可以包含的近似最大字節數。
count 循環序列的文件數量。
append 新構造的文件處理器對象應該追加在一個已存在的日誌文件尾部,則爲true。
java.util.logging.LogRecord 1.4:
·Level getLevel()
獲得這個日誌記錄的記錄級別
·String getLoggerName();
獲得正在記錄這個日誌記錄的記錄器的名字。
·ResourceBundle getresourceBundle();
·String getresourceBundleName();
獲得用於本地化消息的資源包或資源包的名字。如果沒有獲得,則返回null。
·String getMessage();
獲得本地化和格式化之前的原始消息。
·Object[] getParameters();
獲得參數對象。如果沒有獲得,則返回null。
·Throwable getThrown();
獲得被拋出的對象。如果不存在,則返回null。
·String getSourceClassName();
·String getSourceMethodName();
獲得記錄這個日誌記錄的代碼區域。這個消息有可能是由日誌記錄代碼提供的。也有可能是自動從運行時堆棧推測出來的。如果日誌記錄代碼提供的值有誤,或者運行至代碼由於被優化而無法推測出確切位置,這兩個方法的返回值就有可能不準確。
·long getMills();
獲得創建時間。以毫秒爲單位(從1970年開始)
·long getSequenceNumber();
獲得這個日誌記錄的唯一序列序號。
·int getThreadID();
獲得創建這個日誌記錄的線程的唯一ID。這些ID是由LogRecord類分配的,而且與其他線程的ID無關。
java.util.logging.Filter 1.4:
·boolean isLoggable(LogRecord record)
如果給定日誌記錄需要記錄,則返回true。
java.util.logging.Formatter 1.4:
·abstract string format(LogRecord record)
返回對日誌記錄格式化後得到的字符串。
·String getHead(Header h);
·String getTail(Header h);
返回應該出現在包含日誌記錄的文檔的開頭和結尾的字符串。超類Formatter頂一個了這些方法,它們只返回空字符串。如果必要的話,可以對它們進行覆蓋。
·String formatMessage(LogRecord record);
返回經過本地化和格式化後的日誌記錄的消息內容。
調試技巧和建議
1、可以用下面的方法打印或記錄任意變量的值:
Sytem.out.println(“x=” + x);
或
Logger.getGlobal().info(“x=” + x);
如果x是一個數值,則會被轉換成等獎的字符串。如果x是一個對象,那麼Java就會調用這個對象的toString方法。想要獲得隱式參數對象的狀態,就可以打印this對象的狀態。
Logger.getGlobal().info(“this=” + this);
2、一個不太爲人所知但卻非常有效的技巧是在每一個類中放置一個main方法。這樣就可以對每一個類進行單元測試。
public class myClass {
methods and fields
...
public static void main(String[] args) {
test code;
}
}
利用這種技巧,只需要創建少量的對象,調用所有的方法,並檢測每個方法是否能夠正確地運行就可以了。靈位,可以爲每個類保留一個main方法,然後分別爲每個文件調用java虛擬機運行測試。Java虛擬機值調用啓動類的main方法。
3、JUnit是一個非常常見的單元測試框架,利用它可以很容易地組織幾套測試用例。只要修改類,就需要運行測試。在發現bug時,還需要補充一些其他的測試用例。
4、日誌代理(logging proxy)是一個子類的對象,它可以竊取方法調用,並進行日誌記錄,然後調用超類中的方法。例如,如果在調用一個面板的setBackground方法時出現了問題,就可以按照下面的方式,以匿名子類實例的形式創建一個代理對象:
Random generator = new Random() {
public double nextDouble() {
double result = super.nextDouble();
Logger.getGlobal().info(“nextDouble: “ + result);
return result;
}
}
當調用nextDouble方法時,就會產生一個日誌消息。要想知道誰調用了這個方法,就要生成一個堆棧跟蹤。
5、利用Throwable類提供的printStackTrace方法,可以從任何一個異常對象中獲得堆棧情況。下面的代碼將捕獲任何異常,打印異常對象和堆棧跟蹤,然後重新拋出異常,以便能夠找到相應的處理器。
try {
...
} catch (Throwable t) {
t.printStackTrace();
throw t;
}
不一定要通過捕獲異常才能生成堆棧跟蹤。只要在代碼的任何位置插入下面這條語句就可以獲得堆棧跟蹤:
Thread.dumpStack();
6、一般來說,堆棧跟蹤顯示在System.err上。也可以利用printStackTrace(PrintWriter s)方法將它發送到一個文件中。另外,如果想記錄或顯示堆棧跟蹤,就可以採用下面的方式,將它捕獲到一個字符串中。
ByteArrayOutputStream out = new ByteArrayOutputStream();
new Throwable().printStackTrace(out);
String description = out.toString();
7、通常,將一個程序中的錯誤信息保存到一個文件中是非常有用的。然而,錯誤信息被髮送到System.err中,而不是System.out中。因此,不能通過運行下面的語句獲得它們:
java MyProgram > errors.txt;
而是採用下面的方式捕獲錯誤劉:
java MyProgram 2> errors.txt
要想在同一個文件中同時捕獲System.err和System.out,需要使用下面這條命令:
java MyProgram >& errors.txt
這條命令將工作在Windows shell中。
8、讓非捕獲異常的堆棧跟蹤出現在System.err中並不是一個很理想的方法。如果在客戶端偶然看到這些信息,則會感到迷惑,並且在需要的時候也無法實現診斷目的。比較好的方式是將這些內容記錄到一個文件中。可以調用靜態的Thread.setDefaultUncaughtExceptionHandler方法改變非捕獲異常的處理器:
Thread.setDefaultUncaughtExceptionHandler(
new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t,
Throwable th) {
save information in log file.
}
}
});
9、要觀察類的加載過程,可以使用-verbose標識啓動Java虛擬機。這樣就可以看到如下所示的輸出結果:
[Opened /usr/local/jdk5.0/jre/lib/rt.jar]
[Opened /usr/local/jdk5.0/jre/lib/jsse.jar]
[Opened /usr/local/jdk5.0/jre/lib/jce.jar]
[Opened /usr/local/jdk5.0/jre/lib/charsets.jar]
[Loaded java.lang.Object from shared objects file]
[Loaded java.io.Serializable from shared objects file]
[Loaded java.lang.Comparable from shared object file]
[Loaded java.lang.CharSequence from shared objects file]
...
有時候,這種方法有助於診斷由於類路徑引發的問題。
10、Xlint選項告訴編譯器對一些普遍容易出現的代碼問題進行檢查。例如如果使用下面這條命令編譯:
javac -Xlint:fallthrough
當switch語句中缺少break語句時,編譯器就會報告(術語“lint”最初用來描述一種定位C程序中潛在問題的工具、現在通常用於描述查找可以但不違背語法規則的代碼問題的工具。)
11、Java虛擬機增加了對Java應用程序進行監控和管理的支持。它允許利用虛擬機中的代理專職跟蹤內存消耗、線程使用、類加載等情況。這個功能對於先像應用程序服務器這樣大型的、長時間運行的Java程序來說特別重要。下面是一個能夠展示這種功能的例子:JDK加載了一個稱爲jconsole的圖形工具,可以用於顯示虛擬機性能的統計結果。在UNIX/Linux環境下,運行ps實用工具,在Windows環境下,使用任務管理器。然後運行jconsole程序:
jconsole processID
12、可以使用jmap使用工具獲得一個堆的轉儲,其中顯式了堆中的每個對象。使用命令如下:
jmap -dump:format=b, file=dumpFileName processID
jhat dumpFileName
然後,通過瀏覽器進入localhost:7000,將會運行一個網絡應用程序,藉此探查存儲對象時堆的內容。
13、如果使用-Xprof標誌運行Java虛擬機,就會運行一個基本的剖析器來跟蹤那些代碼中經常被調用的方法。剖析信息將發送給System.out。輸出結果中還會顯示那些方法是由即時編譯器編譯的。
GUI程序排錯技巧
1、如果查看Swing窗口,想知道設計者如何將所有這些組件排列得如此整齊,可以監視器內容。按下Ctrl+Shift+F1,會按照層次結構打開所有組件的信息。
2、如果定義了自己定製的Swing組件,但它看起來不能正確顯示,對此有一個很好的工具:Swing圖形調試工具(Swing graphics debugger)。即使你並沒有編寫自己的組件類,能看到組件的內容如何繪製也很有意義,而且很有趣。要對一個Swing組件進行表示,可以使用JComponent類的setDebugGraphicsOptions方法。有以下可用選項:
DebugGraphics.FLASHOPTION 繪製各線段、矩形和文本之前,用紅色閃爍顯示。
DebugGraphics.LOG_OPTION 爲各個繪製操作分別打印一個消息。
DebugGraphics.BUFFERED_OPTION 顯示在屏幕外緩衝區上完成的操作。
DebugGraphics.NONE_OPTION 關閉圖形調試。
我們發現,要讓閃爍選項工作,不許禁用“雙重緩衝”(Swing採用這種策略來緩解更新窗口時的屏幕抖動現象)。要打開閃爍選項,可以使用一下“魔咒”:
RepaintManager.currentManager(getRootPane()).
setDoubleBufferingEnable(false);
((JComponent) getContentPane()).
setDebugGraphicsOptions(DebugGraphics.FLASH_OPTION);
只需要將這幾行代碼放在框架構造函數的最後。程序運行時,你會看到窗格緩慢地填充。或者,如果要完成更多本地化調試,只需要對單個組件調用setDebugGraphicsOptions。要控制閃爍,可以設置持續時間、次數和閃爍顏色。
3、如果想得到GUI應用中生成的各個AWT時間記錄,可以在發出事件的各個組件中安裝一個監聽器。藉助反射的強大能力,可以很容易地自動實現這一點。
4、Robot類可以將案件和點擊鼠標的是假你發送給AWT程序,並能夠對用戶界面自動地檢測。
5、要想獲得Robot對象,首先要得到一個GraphicsDevice對象。通過下面的一系列調用獲得默認的屏幕設備:
GraphicEnvironment environment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice screen =
environment.getDefaultScreenDevice();
然後構造一個Robot對象:
Robot robot = new Robot(screen);
要想發送一個按鍵的事件,就要告訴robot模擬按下鍵和釋放建的動作:
robot.keyPress(KeyEvent.VK_TAB);
robot.keyRelease(KeyEvent.VK_TAB);
要想發送一個點擊鼠標的事件,首先要模擬移動鼠標的時間,然後在模擬按下和釋放鼠標的事件:
robot.mouseMove(x, y);
// x and y are absolute screen pixel coordinates.
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
這裏的思路是:模擬鍵盤和鼠標的輸入,然後進行屏幕快照,以便查看應用程序是否實施了預期的操作。可以調用createScreenCapture方法實現捕捉屏幕的操作:
Rectangle rect = new Rectangle(x, y, width, height);
BufferedImage image = robot.createScreenCapture(rect);
矩形座標使用的也是絕對像素座標。
最後,通常希望在上面的各條命令之間加上一個短暫的演示,以保證程序能夠捕獲到各個時間。設置延遲的方法是:調用delay方法,並傳遞一個以毫秒爲單位的延遲時間,例如:
robot.delay(1000); // delay by 1000 milliseconds
6、Robot類自身並不適用與進行用戶界面的測試。實際上,它是用於構建基本測試工具基礎構件。一個專業的測試工具應該具有捕獲、存儲和再現用戶交互場景的功能,並能夠確定組件在名目中的位置,以及調整鼠標點擊位置。
7、常用方法:
java.awt.GraphicsEnvironment 1.2:
·static GraphicsEnvironment getLocalGraphicsEnvironment();
返回本地圖形環境。
·GraphicsDevice getDefaultScreenDevice();
返回默認的屏幕設備。需要注意的是,使用多態監視器的計算機,每一個屏幕有一個圖形設備。通過調用getScreenDevices方法可以得到一個保存所有屏幕的數組。
java.awt.Robot 1.3:
·Robot(GraphicsDevice device);
構造一個能夠與給定設備交互的Robot對象。
·void keyPress(int key);
·void keyRelease(int key);
模擬按下或釋放按鍵。
參數:key 鍵碼。
·void mouseMove(int x, int y);
模擬移動鼠標。
參數:x, y 用絕對像素座標表示的鼠標位置。
·void mousePress(int eventMask);
·void mouseRelease(int eventMask);
模擬按下或釋放鼠標鍵。
參數:eventMask 描述鼠標鍵事件的掩碼。
·void delay(int milliseconds);
根據給的你給毫秒數延遲robot。
·BufferedImage createScreenCapture(Rectangle rect);
截取屏幕的一部分。
參數:rect 用絕對像素座標表示的所截取的矩形。