轉自(原文地址)
java的try-finally給我們提供了一個“保證某個動作必然執行”的機會。
一個try-finally結構,只要try塊開始執行了,finally塊裏面的代碼保證執行一次並且只有一次。
打個比方,就象你上廁所,只要你一旦開始拉了,我們保證無論如何,是拉稀了也好,放屁了也罷,最終你肯定是擦了屁股走出衛生間。
應用try-finally,我們可以在異常滿天飛的程序裏保證我們的關鍵資源被按時正確清理。一個最常見的應用就是jdbc的Connection, Statement, ResultSet等。
但是,我最近驚奇地發現,不知道怎麼正確清理資源的人大有人在,即使是一些java老手。
看一個例子先:
代碼
void f(){
Connection conn = ...;
Statement stmt = conn.createStatement();
ResultSet rset = ...;
...
}
典型的jdbc程序。但是也是典型的光着屁股,其臭如蘭地走出廁所的典範。哎,你擦屁股了嗎?
有的哥們振振有辭:我不用管,我的jdbc driver/我的應用服務器/garbage collector會處理的。
這是典型的糊塗蛋邏輯。沒有close(),jdbc driver, 應用服務器怎麼知道你是拉完了,還是光着屁股出去接個電話先?難不成這driver都智能地會算命了?
garbage collector倒確實管得了。不過,garbage collector不一定運行啊。你要是有10G得內存,要是你的程序就用了10M,garbage collector說不定就一直睡大覺。而且,就算它管,也許等你光着屁股上班被警察抓起來之後才匆匆趕到,你等的起嗎?
好,有人說,那我擦,我擦,我擦擦擦。行了吧?
代碼
void f(){
Connection conn = ...;
Statement stmt = conn.createStatement();
ResultSet rset = ...;
rset.close();
conn.close();
...
}
呵呵。我的傻哥們,你只擦了靠近後背的那三公分,剩下的嘛,別人看不見你就樂得省土塊兒了是麼?
按jdbc標準,ResultSet, Statement, Connection都要close(),也許有的driver會在Connection關閉的時候同時正確清理ResultSet, Statement,但是,並沒有一條規定讓所有的driver都這麼做。
另外,也許你的Connection是從一個池裏面來的,它只是回到池中去,如果你不關閉Statement, ResultSet,下一個拿到這個Connection的人也許就倒黴了!
做事要有始有終,既然開始擦了,就擦乾淨點兒,行不?(那個,誰誰誰,借我個防毒面具先!)
ok,有個講衛生的小傻子這樣擦:
代碼
void f(){
Connection conn = ...;
Statement stmt = conn.createStatement();
ResultSet rset = ...;
rset.close();
stmt.close();
conn.close();
...
}
然後洋洋得意地說:我是好孩子,我天天擦屁屁。
是啊,多聽話的孩子呀。可惜,某天,這孩子正坐在馬桶上美着呢,媽媽喊了嗓子:二傻子,吃飯啦。
哦!吃飯。二傻子褲子都沒提就竄出來了,薰得媽媽一個跟頭。
什麼問題,傻子做事一根筋,不能打擾,一旦有異常情況出現,屁股就忘了擦了。
所以,我這裏鄭重提醒大家,請用"try-finally"!它獨有凹槽,防止側漏...(糟了,串臺了)
是啊,java老手們都不是傻子,都知道用try-finally的,可是,別美,你現在就保不齊擦沒擦屁股呢!
常見擦法:
代碼
void f(){
Connection conn = null;
Statement stmt = null;
ResultSet rset = null;
try{
conn = ...;
stmt = ...;
rset = ...;
...
}
finally{
if(rset!=null)rset.close();
if(stmt!=null)stmt.close();
if(conn!=null)conn.close();
}
}
嗯。怎麼說呢。挺聰明的。都學會if(xxx!=null)這種傳說中條件判斷的上古絕學了。
可惜,你屁股大,一張紙不夠,你用了第一張紙,滿意地看着它圓滿地完成了金燦燦的任務,再用第二張,靠,只太薄,破了,一手金燦燦地,象帶了個金戒指。你大怒,起,絕塵而去。於是也忘了第三張紙,
哥們兒,close()是可以出異常的,你rset關了,stmt.close()出現了異常,但是conn就不管了?
近日有位室外高人,據說是鬼谷子高徒,鑑於憐我世人,不擦屁股的實多的高尚情操,親手賺寫一本絕世擦功祕籍,其文美,其意高,除了擦不乾淨之外,真可以說是稱霸擦林。
代碼
void close(Connection conn){
try{
if(conn!=null) conn.close();
}
catch(Exception e){
e.printStackTrace();
}
}
void close(ResultSet rset){
...
}
void close(Statement rset){
...
}
void f(){
Connection conn = null;
Statement stmt = null;
ResultSet rset = null;
try{
conn = ...;
stmt = ...;
rset = ...;
...
}
finally{
close(rset);
close(stmt);
close(conn);
}
}
哈,你們不能紙擦破了就不接着擦啊,甚至大而化之,不能擦股用具有了問題就半途而廢呀!
具信,該高人以此法擦遍天下凡十數載,未有擦而無功者。
可惜,高人卻忽視了,除了紙會出故障,甚至大而化之,一切擦具(如土塊兒,木條兒,手指)都可能出現故障,還有別的地方也會出故障地!
除了Exception,還有Error啊,我的高人!如果close(rset)拋了一個Error,你的close(stmt), close(conn)不都歇菜了?
後來,高人在《絕世武功補遺》裏面解釋說:Error代表不可恢復錯誤,說明整個排泄大業都受阻了,所以根本不應該試圖對這種情況做任何處理,你也處理不了(自然也隱含此時你也根本無法擦屁股了的論斷)。任何試圖在這種情況下仍然固執擦屁股的做法都是倒行逆施,螳臂當車,必然被歷史的車輪所攆碎。
此書一處,天下辟易。其革命性之深遠,難以估量。具有關方面評論,Sun這個公共廁所的try-finally這個工具的設定本身就是不合理的,應該被歷史車輪攆碎的,因爲try-finally居然試圖在出現Error的時候去做一些事情!是可忍,孰不可忍?
可以預見,try-finally將被sun徹底廢棄,並且向廣大公衆做公開道歉以檢討多年來的欺騙造成的惡劣影響。
另外,公廁的構造也受到質疑,因爲一旦有一個拉客在擦的時候某一步無可挽回地失敗(比如,太緊張,手一抖,紙掉到了坑裏,又死活伸手撈不着),那麼他就大搖大擺不再繼續擦,而如果碰巧此人剛吃了蘿蔔,就會把整個廁所裏的其它拉客都薰得無法繼續。(想想一個app server吧。你一個程序歇菜,樂得請病假不擦了,別人也跟着倒黴?)
嘿嘿,那麼,你擦了嗎?你肯定你擦了?擦乾淨了?
幸好,我們翻遍上古祕籍,最終在北京山頂洞人的失傳寶典《呼呼,擦!》中發現了一個據稱絕對乾淨的擦法,那就是------------
一下一下擦!
具體操作辦法如下:
代碼
void f(){
final Connection conn = ...;
try{
final Statement stmt = ...;
try{
final ResultSet rset = ...;
try{
...
}
finally{rset.close();}
}
finally{stmt.close();}
}
finally{conn.close();}
}
其訣竅就是,每建立一個需要清理的資源,就用一個try-finally來保證它可以被清理掉。
如此,任何時候,你都是屁股乾乾靜靜地離開衛生間。
哪。好多聖人門徒跟我說:這樣一下一下擦,姿勢非常不雅觀(看看那嵌套的try塊吧),有違古禮。我們反對!
靠,你說孔醜兒古還是山頂洞人古??
屁股還泛着味兒呢,還拽什麼“雅”?
而且,要是死要面子,也可以拉個簾子,擦的時候別讓人看見嘛。比如:
代碼
interface ResultListener{
void call(ResultSet rset);
}
class SqlReader{
void read(ResultListener l){
final Connection conn = ...;
try{
final Statement stmt = ...;
try{
final ResultSet rset = ...;
try{
l.call(rset);
}
finally{rset.close();}
}
finally{stmt.close();}
}
finally{conn.close();}
}
}
這一下一下擦的動作都藏在SqlReader這個簾子裏,你直接在ResultListener裏面拉不就行了?
那位高人說了,這太複雜,就爲了擦個屁股不值。
這個嘛,值不值的另說,你那個簡單,就是簡簡單單地擦不乾淨屁股。要不您乾脆別擦得了,更簡單呢還。反正您出門兒就愣說擦的是Chanel香水兒就是了。有啥比瞪眼兒說白話兒簡單?
對了, 我還忘了一個條款:
就是擦屁股的時候按順序擦。誰後進廁所的,要讓人家先出去。
“什麼狗屁規則?“那位問了。
這個這個--,啊,你猜猜~~~?
嗯,對了,是這樣的,上廁所都不着急,姍姍來遲,上課更不着急,更喜歡遲到了,對不對?而誰上課天天遲到早退還不擔心畢業?當然是了,是不?
人家都了,你還不讓人家先出去?活膩味了你?(此處尾音要拉長,而且向上拐)
反正啊,具體說,ResultSet最後創建,但是要先關。
Statement其次。Connection最後。
當然了,也許在你的環境下,次序錯了也沒出事情。但是,咱麼吃軟飯的(吃軟件這口飯的)圖啥?不就圖個放心嗎?上廁所圖啥?不就圖個別讓抓去當兔子嗎?
也許某個driver對次序不敏感,但是不好說哪天你換個環境就忽然她奶奶的敏感了呢?
比如吧,你有connection pool, conn.close()把connection返回到connection。
你要是先conn.close(),好嘛,connection先回到pool了,正好別的線程裏面等着要connection,立馬這個connection又給分配出去了。
這下齊了,你statement, resultset還沒關呢,那邊事故單位領導就找上門了。什麼香油油的桌子,什麼桐油炸丸子,全給你送來了。這不添堵嗎?
好在,在我們《呼呼,擦!》寶典中記載的“一下一下擦”神功,老少咸宜,童叟無欺,有道是:法擦大法好,不如法擦冰箱好!
跑題了。反正是,只要你一個try-finally對應一個資源,你就不可能在次序上出錯。自然而然的就是後入先出的堆棧結構。
反觀別的擦法,就沒有這個效果,次序如何,全靠你自己掌握。弄錯了,系統也不告訴你。等着吃桐油炸丸子吧。
這也是我們推廣一下一下擦的一個原因。