作者簡介:Peter Haggar是IBM在北卡羅來納州的Research Triangle Park的一名高級軟件工程師,他發表了無數篇關于 Java 編程的文章。他有著廣泛的編程經驗,曾致力于開發工具、類庫和操作系統的相關工作。Peter 在IBM從事新興因特網技術方面的工作,目前主要從事有關高性能Web服務方面的工作。Peter經常在很多業界會議上作為技術發言人就 Java 技術發表言論。他已經為IBM工作了15年多,并獲得了Clarkson University的計算機科學學士學位。
?
本書匯集了Java編程實踐方面的建議、忠告、范例和討論,本書的組織是一個個獨立的課程,每個課程稱為實踐(PRAXIS),用以討論特定主題,每個實踐按各自獨立的方式撰寫。本身詳細分析了某些設計和編程方面的問題,挑選的依據是編程實踐上的有效和高效。Java最被人抱怨的一點是性能,因此本書以最大的篇幅討論這一主題,使Java代碼運行得更高效。
General Techniques
?
PRAXIS1:
引數以by value方式而非by reference方式傳遞
所有Java對象都通過object reference被訪問,常見的一個誤解是java以by reference方式傳遞引數,
事實上所有引數都以by value方式傳遞。
?
比如int參數,在函數內修改了,在調用后并沒有修改
而object則不是這樣,函數內修改了,調用后也是修改的,若不想這樣,可以把object改為不可變的或拷貝object
?
PRAXIS2:
對不變的data和object references使用final
為了讓data或object reference成為不變量(常數),請使用final。注意,final僅僅令object reference
自身成為不變量,并不限制它所指對象的改變。
?
PRAXIS3:
缺省情況下所有non-static函數都可被覆蓋
缺省情況下,所以non-static函數都可以被subclass覆蓋,但如果加上關鍵字final,便可防止被subclass覆蓋。
聲明某個類為final,就表示這個類的所有函數都是final的,可以阻止它派生子類。
?
PRAXIS4:
在arrays和Vectors之間慎重選擇
arrays
和vectors是常見的容器類(storage classes)。選用它們之前應該先了解它們的功用和特性。
?
如果超出了array的大小,會報數組越界異常,基本類型的array會自動為數組內的元素賦默認值,而對象數組則是null。而vector長度會自動增長,vector.size()返回的元素的數量,不是固定值,vector的內部實現是array實現的,vector只能容納對象。
一般來說,array速度更快。
?
PRAXIS5:
多態優于instanceof
instanceof
的許多用途可以因為改用多態而消失,使用多態,代碼將更清晰、更易于擴展和維護。
?
PRAXIS6:
必要時才使用instanceof
有時我們無法回避使用instanceof。我們應該了解什么情況下必須使用它。
?
PRAXIS7:
一旦不再需要object references,就將它設為null
不要忽視內存可能帶來的問題,盡管有了垃圾回收機制,你仍然需要關注你的代碼如何運用內存,如果能夠領悟垃圾回收和內存運用細節,你就能夠更好的知道何時應該將object references設為null,那將導致高效的代碼。
Objects and Equality
?
PRAXIS8:
區分reference type和primitive type
Java
是面向對象語言,但其操控的東西并非都是對象(objects)。理解reference type和primitive types之間的差異,及它們在JVM中的表述,會使你在運用它們時得以做出明智的選擇。
?
PRAXIS9:
區分==和equals()
==
用來測試基本型別的相等性,亦可判定兩個object references是否指向同一個對象,但若要測試
values
(值)或semantic(語義)相等,應使用equals()。
?
PRAXIS10:
不要依賴equals()的缺省實現
不要不假思索的認定一個class總是會正確的實現出equals(),此外,java.lang.Object提供的equals()大多數時候并非進行你想要的比較。
?
PRAXIS11:
實現equals()時必須深思熟慮
如果某個class所生的兩個對象“即使不占用相同的內存空間,也被視為邏輯上相等”,那么就該為這個class提供一個equals()。
?
PRAXIS12:
實現equals()時優先考慮使用getClass()
實現equals()時請優先考慮采用getClass()。畢竟,“隸屬同一個class下的對象才得被視為相等”是正確實現equals()的一個簡明方案。
?
惟有相同class所產生的對象才可以被視為相等,可以只比較某些關鍵屬性。
?
PRAXIS13:
調用super.equals()以喚起base class的相關行為
任何base class(除了java.lang.Object)如果實現equals(),其derived class都應該調用super.equals()。
?
PRAXIS14:
在equals()函數中謹慎使用instanceof
惟有當你考慮允許“一個derived class對象可以相等于其base class對象”時,才在equals()中使用instanceof。使用這項技術前請先弄清楚其影響。
?
PRAXIS15:
實現equals()時需遵循某些規則
撰寫equals()并非那么直觀,如果想要恰當實現出equals(),請遵循某些規則。
?
1.
如果某個class的兩個對象即使占據不同的內存空間,也可被視為“邏輯上相等”的話,那么你得為這個class提供一個equals()。
2.
請檢查是否等于this。
3.
比較這個class中的相關屬性,以判斷兩個對象是否相等。
4.
如果有java.lang.Object以外的任何base class實現了equals(),那么就應該調用super.equals()。
Exception Handling
?
PRAXIS16:
認識“異??刂屏鳌保?span lang="EN-US">exception control flow)機制
了解異常控制流程細節,了解這些細微之處有助于你回避問題。
?
PRAXIS17:
不要忽略異常
一旦異常出現卻沒有被捕獲,拋出異常的那個線程就會中止運行,是的,異常意味錯誤,永遠不要忽略它。
?
PRAXIS18:
不要隱藏異常
如果處理異常期間又從catch或finally區段拋出異常,原先的異常會因而被隱藏起來,一旦發生這樣的事情,就會丟失錯誤信息,你應當撰寫專門負責處理這種情形的代碼,將所有異常回傳給調用者。
?
只有一個異??梢詡鞑サ酵饨?,可以把所有的異常加入到一個vector內。
?
PRAXIS19:
理解throws子句的缺點
將一個異常加入某函數的throws子句,會影響該函數的所有調用者。
?
PRAXIS20:
細致而全面的理解throws子句
任何函數的throws子句應當列出它所傳播的所有異常,包括衍生異常型別(derived exception types)。
?
覆蓋一個方法時,throws的異常受到約束
要么不拋出異常,要么和被覆蓋方法一樣類型的異常,要么是被覆蓋方法異常的派生異常。
?
PRAXIS21:
使用finally避免資源泄漏
不要忽視內存以外的資源,垃圾回收機制不會替你釋放它們,請使用finally確保內存以外的資源被釋放。
?
PRAXIS22:
不要從try塊中返回
不要從try區段中發出return語句,因為這個函數未必會立即從那兒返回,如果存在finally區段,它就會被運行起來并可能改變回傳值。
?
PRAXIS23:
將try/catch代碼塊置于循環外
撰寫含有異常處理的循環時,請將try和catch區段置于循環外部,在某些實現版本上,這會產生更快的運行代碼。
?
PRAXIS24:
不要將異常用于流程控制
請將異常用于預期行為之外的情況,不要以異常來控制流程,請采用標準的語言流程構件,這樣的流程表達會更清晰更高效。
?
PRAXIS25:
不要每逢出錯就使用異常
只有面對程序行為可能出乎意料的情況下才使用異常,“預期中的行為”應使用返回代碼來處理。
?
PRAXIS26:
在構造函數中拋出異常
盡管構造函數并非函數(method),因而不能回傳一個值,但構造函數有可能失敗,如果它們失敗了,請拋出一個異常。
?
PRAXIS27:
拋出異常之前先將對象恢復為有效狀態(valid state)
拋出異常很容易,困難的是“將異常所引發的傷害減到最小”,拋出異常前,應確?!叭绻惓1惶幚砗?,流程再次進入拋出異常的那個函數中,該函數可以成功完成”。
Performance
?
PRAXIS28:
先把焦點放在設計、數據結構和算法身上
給java帶來最大性能提升的辦法就是:在設計和算法中使用與語言無關的技術,因此,首先請將你的精力集中在這上面。
?
PRAXIS29:
不要依賴編譯期的優化技術
由java編譯器生成的代碼,通常不會比你自己撰寫的更好,別指望編譯器能夠多么優化你的源碼。
?
PRAXIS30:
理解運行期的代碼優化技術
Java
對性能的大部分努力都圍繞著“運行期優化”展開,這種做法有利無弊。
?
PRAXIS31:
如欲進行字符串的連接,StringBuffer優于String
對于字符串的連接,StringBuffer要比String快許多倍。
?
PRAXIS32:
將對象的創建成本降至最小
在許多面向對象系統中,“創建對象”意味著高昂的成本,了解成本所在,以及了解“加速對象創建速度”的技術,都可以導致更快速的程序。
?
PRAXIS33:
謹防未使用的對象
非必要別產生對象,否則會減慢你的程序速度。
?
PRAXIS34:
將同步(synchronization)減至最低
聲明synchronized函數或synchronized區段,會顯著降低性能,應該只在對象有所需要時才使用同步機制。
?
PRAXIS35:
盡可能使用stack變量
stack
變量為JVM提供了更高效的byte code指令序列,所以在循環內重復訪問static變量或instance變量時,應當將它們暫時存儲于stack變量中,以便獲得更快的運行速度。
?
PRAXIS36:
使用static、final和private函數以促成inlining
以方法體替換方法調用,會導致更快速的程序,如果要令函數為inline,必須先聲明它們為static、final或private。
?
PRAXIS37:instance
變量的初始化一次就好
由于所有static變量和instance變量都會自動獲得缺省值,所以不必重新將它們設為缺省值。
?
PRAXIS38:
使用基本類型(primitive types)使代碼更快更小
使用基本類型,比使用其包裝類,產生的代碼又小又快。
?
PRAXIS39:
不要使用Enumeration或Iterator來遍歷Vector
遍歷Vector時,請使用get()函數而非Enumeration或Iterator。這樣做會導致更少的函數調用,意味程序速度更快。
?
PRAXIS40:
使用System.arraycopy()來復制arrays
這個是本機(native)函數,速度更快。
?
PRAXIS41:
優先使用array,然后才考慮Vector和ArrayList
如果你需要Vector的功能但不需要它的同步特性,可改用ArrayList。
?
PRAXIS42:
盡可能復用(reuse)對象
復用現有對象,幾乎總是比創建新對象更劃算。
?
PRAXIS43:
使用延遲求值(lazy evaluation)
如果某個成本高貴的計算并非一定必要,就盡量少做,使用懶加載技術避免那些永遠不需要的工作。
?
PRAXIS44:
以手工方式將代碼優化
由于Java編譯器在優化方面的作為甚少,為了生成最佳byte code,請以手工方式將你的源碼優化。
?
PRAXIS45:
編譯為本機代碼(native code)
編譯為本機代碼,通常可以獲得運行速度更快的代碼,但你卻因此必須在各種不同的本機方案中取舍。
?
Multithreading
?
PRAXIS46:
面對instance函數,synchronized鎖定的是對象(object)而非函數(method)或代碼
關鍵字synchronized鎖定的是對象,而非函數或代碼,一個函數或程序區段被聲明為synchronized,并不意味同一時刻只能由一個線程運行它。
?
PRAXIS47:
弄清楚synchronized statics函數與synchronized instance函數之間的差異
兩個函數被聲明為synchronized,并不就意味它們是“多線程安全”,對instance函數或object reference同步化,與對static函數或class literal(字面常數)同步化相比,得到的lock全然不同。
?
PRAXIS48:
以private數據(field)+相應的訪問函數(accessor)替換public/protected數據
如果沒有適當保護你的數據,用戶便有機會繞過你的同步機制。
?
PRAXIS49:
避免無謂的同步控制
一般情況下請不要同步化所有函數,同步化不僅造成程序緩慢,并且喪失了并發(concurrency)的可能,
請采用“單對象多鎖”技術以允許更多并發動作。
?
PRAXIS50:
訪問共享變量時請使用synchronized或volatile
不可切割(原子化,atomic)操作并非意味“多線程安全”,JVM實現品被允許在私有內存中保留變量的工作副本,這可能會產生陳舊數據,為避免這個問題,請使用同步化機制或將變量聲明為volatile
?
PRAXIS51:
在單一操作中鎖定所有用到的對象
同步化某一函數,并不一定就會使其成為“多線程安全”,如果synchronized函數操控著多個函數,而它們并不都是此函數所屬class的private instance data,那么你必須對這些對象自身也進行同步化。
?
PRAXIS52:
以固定而全局性的順序取得多個locks,以避免死鎖(deadlock)
當你同步化多個對象,請以固定、全局性的順序獲得locks,以避免死鎖。
?
PRAXIS53:
優先使用notifyAll()而非notify()
notify()
只喚醒一個線程,要想喚醒多個線程,請使用notifyAll()。
?
PRAXIS54:
針對wait()和notifyAll()使用旋鎖(spin locks)
當你等待條件變量時,請總是使用旋鎖確保正確結果。
?
PRAXIS55:
使用wait()和notifyAll()替換輪詢循環(polling loops)
將所有polling loops替換為使用wait()、notify()和notifyAll()的spin locks(旋鎖),spin locks直觀而高效,polling loops則慢很多倍。
?
PRAXIS56:
不要對上鎖對象(locked object)的object reference重新賦值
當一個對象被鎖定,有可能其他線程會因同一個object lock而受阻(blocked),假如你對上鎖對象的object reference重新賦值,其他線程內懸而未決的那些locks將不再有意義。
?
PRAXIS57:
不要調用stop()或suspend()
不要調用stop()或suspend(),因為它們可能導致數據內部混亂,甚至引發死鎖。
?
PRAXIS58:
通過線程之間的協作來中止線程
你不應該調用stop(),如欲安全地停止線程,必須要求它們相互協作,才能姿態優雅的中止。
?
Classes and Interfaces
?
PRAXIS59:
運用interfaces支持多重繼承
當你想要支持interface的單一繼承或多重繼承,或想要實現一個標識型的interface時,請使用interfaces。
?
PRAXIS60:
避免interfaces中的函數發生沖突
沒有任何辦法能夠阻止兩個interfaces使用同名的常數和函數,為了避免可能的沖突,應當小心命名常數和函數。
?
PRAXIS61:
如需提供部分實現(partial implementation),請使用抽象類(abstract classes)
使用abstract class來為一個class提供部分實現,這些實現很可能對derived class是共通的。
?
PRAXIS62:
區分interface、abstract class和concrete class
一旦正確理解interface、abstract class和concrete class的差異,你就可以在設計是編碼時做出正確的選擇。
?
PRAXIS63:
謹慎定義和實現不可變類(immutable classes)
如果你希望對象內容永遠不被改動,請使用不可變對象(immutable object),這種對象自動擁有“多線程安全性”。
?
PRAXIS64:
欲傳遞或接收可變對象(mutable objects)的object references時,請使用clone()
為了保證immutable objects,你必須在傳入和回傳它們時對它們施行clone()。
?
PRAXIS65:
使用繼承或委托(delegation)來定義不可變類(immutable classes)
使用immutable interface、common interface或base class,或是immutable delegation classes,來定義immutable classes。
?
PRAXIS66:
實現clone()時記得調用super.clone()
當你實現一個clone(),總是應該調用super.clone()以確保產生正確的對象。
?
PRAXIS67:
別只依靠finalize()清理non-memory(內存之外)的資源
你不能保證finalize()是否被調用,以及何時被調用,因此,請專門實現一個public函數來釋放內存以外的資源。
?
PRAXIS68:
在構造函數內調用non-final函數時要小心
如果一個non-final函數被某個derived class覆蓋,在構造函數中調用這個函數可能會導致不可預期的結果。
?