你覺得自己是一個Java專家嗎?是否肯定自己已經全面掌握了Java的異常處理機制?在下面這段代碼中
你能夠迅速找出異常處理的六個問題嗎?
OutputStreamWriter out =
java
sql
Connection conn =
try { // ⑸
Statement stat = conn
createStatement();
ResultSet rs = stat
executeQuery(
select uid
name from user
);
while (rs
next())
{
out
println(
ID
+ rs
getString(
uid
) // ⑹
姓名
+ rs
getString(
name
));
}
conn
close(); // ⑶
out
close();
}
catch(Exception ex) // ⑵
{
ex
printStackTrace(); // ⑴
⑷
}
作為一個Java程序員
你至少應該能夠找出兩個問題
但是
如果你不能找出全部六個問題
請繼續閱讀本文
本文討論的不是Java異常處理的一般性原則
因為這些原則已經被大多數人熟知
我們要做的是分析各種可稱為
反例
(anti
pattern)的違背優秀編碼規范的常見壞習慣
幫助讀者熟悉這些典型的反面例子
從而能夠在實際工作中敏銳地察覺和避免這些問題
反例之一丟棄異常 代碼
行
行
這段代碼捕獲了異常卻不作任何處理
可以算得上Java編程中的殺手
從問題出現的頻繁程度和禍害程度來看
它也許可以和C/C++程序的一個惡名遠播的問題相提並論——不檢查緩沖區是否已滿
如果你看到了這種丟棄(而不是拋出)異常的情況
可以百分之九十九地肯定代碼存在問題(在極少數情況下
這段代碼有存在的理由
但最好加上完整的注釋
以免引起別人誤解)
這段代碼的錯誤在於
異常(幾乎)總是意味著某些事情不對勁了
或者說至少發生了某些不尋常的事情
我們不應該對程序發出的求救信號保持沉默和無動於衷
調用一下printStackTrace算不上
處理異常
不錯
調用printStackTrace對調試程序有幫助
但程序調試階段結束之後
printStackTrace就不應再在異常處理模塊中擔負主要責任了
丟棄異常的情形非常普遍
打開JDK的ThreadDeath類的文檔
可以看到下面這段說明
特別地
雖然出現ThreadDeath是一種
正常的情形
但ThreadDeath類是Error而不是Exception的子類
因為許多應用會捕獲所有的Exception然後丟棄它不再理睬
這段話的意思是
雖然ThreadDeath代表的是一種普通的問題
但鑒於許多應用會試圖捕獲所有異常然後不予以適當的處理
所以JDK把ThreadDeath定義成了Error的子類
因為Error類代表的是一般的應用不應該去捕獲的嚴重問題
可見
丟棄異常這一壞習慣是如此常見
它甚至已經影響到了Java本身的設計
那麼
應該怎樣改正呢?主要有四個選擇
處理異常
針對該異常采取一些行動
例如修正問題
提醒某個人或進行其他一些處理
要根據具體的情形確定應該采取的動作
再次說明
調用printStackTrace算不上已經
處理好了異常
重新拋出異常
處理異常的代碼在分析異常之後
認為自己不能處理它
重新拋出異常也不失為一種選擇
把該異常轉換成另一種異常
大多數情況下
這是指把一個低級的異常轉換成應用級的異常(其含義更容易被用戶了解的異常)
不要捕獲異常
結論一既然捕獲了異常就要對它進行適當的處理不要捕獲異常之後又把它丟棄不予理睬 反例之二不指定具體的異常 代碼
行
許多時候人們會被這樣一種
美妙的
想法吸引
用一個catch語句捕獲所有的異常
最常見的情形就是使用catch(Exception ex)語句
但實際上
在絕大多數情況下
這種做法不值得提倡
為什麼呢?
要理解其原因
我們必須回顧一下catch語句的用途
catch語句表示我們預期會出現某種異常
而且希望能夠處理該異常
異常類的作用就是告訴Java編譯器我們想要處理的是哪一種異常
由於絕大多數異常都直接或間接從java
lang
Exception派生
catch(Exception ex)就相當於說我們想要處理幾乎所有的異常
再來看看前面的代碼例子
我們真正想要捕獲的異常是什麼呢?最明顯的一個是SQLException
這是JDBC操作中常見的異常
另一個可能的異常是IOException
因為它要操作OutputStreamWriter
顯然
在同一個catch塊中處理這兩種截然不同的異常是不合適的
如果用兩個catch塊分別捕獲SQLException和IOException就要好多了
這就是說
catch語句應當盡量指定具體的異常類型
而不應該指定涵蓋范圍太廣的Exception類
另一方面
除了這兩個特定的異常
還有其他許多異常也可能出現
例如
如果由於某種原因
executeQuery返回了null
該怎麼辦?答案是讓它們繼續拋出
即不必捕獲也不必處理
實際上
我們不能也不應該去捕獲可能出現的所有異常
程序的其他地方還有捕獲異常的機會——直至最後由JVM處理
結論二在catch語句中盡可能指定具體的異常類型必要時使用多個catch不要試圖處理所有可能出現的異常 反例之三占用資源不釋放 代碼
行
行
異常改變了程序正常的執行流程
這個道理雖然簡單
卻常常被人們忽視
如果程序用到了文件
Socket
JDBC連接之類的資源
即使遇到了異常
也要正確釋放占用的資源
為此
Java提供了一個簡化這類操作的關鍵詞finally
finally是樣好東西
不管是否出現了異常
Finally保證在try/catch/finally塊結束之前
執行清理任務的代碼總是有機會執行
遺憾的是有些人卻不習慣使用finally
當然
編寫finally塊應當多加小心
特別是要注意在finally塊之內拋出的異常——這是執行清理任務的最後機會
盡量不要再有難以處理的錯誤
結論三保證所有資源都被正確釋放充分運用finally關鍵詞 反例之四不說明異常的詳細信息 代碼
行
行
仔細觀察這段代碼
如果循環內部出現了異常
會發生什麼事情?我們可以得到足夠的信息判斷循環內部出錯的原因嗎?不能
我們只能知道當前正在處理的類發生了某種錯誤
但卻不能獲得任何信息判斷導致當前錯誤的原因
printStackTrace的堆棧跟蹤功能顯示出程序運行到當前類的執行流程
但只提供了一些最基本的信息
未能說明實際導致錯誤的原因
同時也不易解讀
因此
在出現異常時
最好能夠提供一些文字信息
例如當前正在執行的類
方法和其他狀態信息
包括以一種更適合閱讀的方式整理和組織printStackTrace提供的信息
結論四在異常處理模塊中提供適量的錯誤原因信息組織錯誤信息使其易於理解和閱讀 反例之五過於龐大的try塊 代碼
行
行
經常可以看到有人把大量的代碼放入單個try塊
實際上這不是好習慣
這種現象之所以常見
原因就在於有些人圖省事
不願花時間分析一大塊代碼中哪幾行代碼會拋出異常
異常的具體類型是什麼
把大量的語句裝入單個巨大的try塊就象是出門旅游時把所有日常用品塞入一個大箱子
雖然東西是帶上了
但要找出來可不容易
一些新手常常把大量的代碼放入單個try塊
然後再在catch語句中聲明Exception
而不是分離各個可能出現異常的段落並分別捕獲其異常
這種做法為分析程序拋出異常的原因帶來了困難
因為一大段代碼中有太多的地方可能拋出Exception
結論五盡量減小try塊的體積 反例之六輸出數據不完整 代碼
行
行
不完整的數據是Java程序的隱形殺手
仔細觀察這段代碼
考慮一下如果循環的中間拋出了異常
會發生什麼事情
循環的執行當然是要被打斷的
其次
catch塊會執行——就這些
再也沒有其他動作了
已經輸出的數據怎麼辦?使用這些數據的人或設備將收到一份不完整的(因而也是錯誤的)數據
卻得不到任何有關這份數據是否完整的提示
對於有些系統來說
數據不完整可能比系統停止運行帶來更大的損失
較為理想的處置辦法是向輸出設備寫一些信息
聲明數據的不完整性
另一種可能有效的辦法是
先緩沖要輸出的數據
准備好全部數據之後再一次性輸出
結論六全面考慮可能出現的異常以及這些異常對執行流程的影響 改寫後的代碼
根據上面的討論
下面給出改寫後的代碼
也許有人會說它稍微有點啰嗦
但是它有了比較完備的異常處理機制
OutputStreamWriter out =
java
sql
Connection conn =
try {
Statement stat = conn
createStatement();
ResultSet rs = stat
executeQuery(
select uid
name from user
);
while (rs
next())
{
out
println(
ID
+ rs
getString(
uid
) +
姓名:
+ rs
getString(
name
));
}
}
catch(SQLException sqlex)
{
out
println(
警告
數據不完整
);
throw new ApplicationException(
讀取數據時出現SQL錯誤
sqlex);
}
catch(IOException ioex)
{
throw new ApplicationException(
寫入數據時出現IO錯誤
ioex);
}
finally
{
if (conn != null) {
try {
conn
close();
}
catch(SQLException sqlex
)
{
System
err(this
getClass()
getName() +
mymethod
不能關閉數據庫連接:
+ sqlex
toString());
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26641.html