












有些類(lèi)不希望被實(shí)例化,對它們實(shí)例化也沒(méi)有意義。


因為聲明的構造函數是私有的,所以它在類(lèi)的外部不可訪(fǎng)問(wèn)。假設構造函數不會(huì )被類(lèi)自身從內部調用,即能保證類(lèi)永遠不會(huì )被實(shí)例化。 例:
class F {
private F() {
...
}
...
}
重用同一對象比每次都創(chuàng )建功能等同的新對象通常更適合。重用方式既快速也更加時(shí)尚。








終結程序(finalizer)的行為是不可預測的,而且是危險的,通常也不必要。不要把終結程序作為C++析構函數(destructor)的類(lèi)似物。
終結程序無(wú)法保證能別及時(shí)的執行,這意味著(zhù)不能用終結程序來(lái)處理時(shí)間關(guān)鍵(time-critical)性的操作。例如,依賴(lài)終結程序去關(guān)閉打開(kāi)的文件是一個(gè)嚴重的錯誤,因為打開(kāi)的文件描述符是一種有限資源,而JVM不會(huì )及時(shí)地安排終結程序執行,如果多個(gè)文件處于打開(kāi)狀態(tài),程序有可能會(huì )因為無(wú)法再打開(kāi)文件而執行失敗。
JLS不僅不保證終結程序的及時(shí)執行,它甚至不保證終結程序會(huì )獲得執行。永遠不要依賴(lài)終結程序去更新關(guān)鍵的持續狀態(tài)(persistent state)。例如,依靠終結程序釋放一個(gè)共享資源如數據庫上的持續鎖,將是導致所有分布系統跨掉的絕佳方法。
非可變類(lèi)就是類(lèi)的實(shí)例不能被修改的類(lèi)。如String。

為了使類(lèi)成為非可變的,要遵循下面5條原則





下面是一個(gè)稍微復雜的例子:
public final class Complex {
private final float re;
private final float im;
public Complex(float re, float im) {
this.re = re;
this.im = im;
}
public float realPart() { return re; }
public float imaginaryPart() { return im; }
public Complex add(Complex c) {
return new Complex(re+c.re,im+c.im);
}
...
public boolean equals(Object o) {
if(o==this)
return true;
if(!(o instanceOf Complex))
return false;
Complex c = (Complex) 0;
return(Float.floatToIntBits(re) == Float.floatToIntBits(c.re) ) &&
(Float.floatToIntBits(im) == Float.floatToIntBits(c.im));
}
public int hashCode() {
int result = 17 + Float.floatToIntBits(re);
result = 37*result + Float.floatToIntBits(im);
return result;
}
public String toString() {
return "("+re+" + "+im+"i)";
}
}
這個(gè)類(lèi)表示復數,注意到算術(shù)操作創(chuàng )建和返回一個(gè)新的復數實(shí)例,而不是修改了這個(gè)實(shí)例。





繼承是實(shí)現代碼重用的有力途徑,但它不總是完成這項工作的最后的工具。與方法調用不同,繼承打破了封裝性。子類(lèi)的特有的功能,依賴(lài)于它的超類(lèi)的實(shí)現細節。超類(lèi)的實(shí)現會(huì )隨著(zhù)版本而改變,如果出現這樣的情況,即使不觸動(dòng)子類(lèi)的代碼,它也會(huì )被破壞。
不去擴展現有的類(lèi),而是給類(lèi)增加一個(gè)引用現有類(lèi)實(shí)例的新的私有域,這種設計方法被成為復合(composition),因為現有的類(lèi)成為了新的類(lèi)的一部分。
繼承只有當子類(lèi)確實(shí)是超類(lèi)的“子類(lèi)型”(subtype)時(shí),才是適合的。換句話(huà)說(shuō),對兩個(gè)類(lèi)A和B,如果“B是A”的關(guān)系存在,那么B應該擴展A。在把B擴展A時(shí),問(wèn)這樣一個(gè)問(wèn)題:“任何的B都是A嗎?”,如果答案是否定的,那么通常應該把A作為B的一個(gè)私有實(shí)例,然后暴露更小、更簡(jiǎn)單的API:A不是B的基本部分,只是它的實(shí)現細節。
類(lèi)必須提供文檔準確地描述重載任一方法的效果。類(lèi)必須說(shuō)明它的可重載方法的自用性(self-use):對每個(gè)公共的或受保護的方法或構造函數,它的文檔信息都必須要表明它在調用哪一個(gè)可重載的方法、以什么順序調用及每一個(gè)調用的結果如何影響后面的處理。
為了允許程序員有效地進(jìn)行子類(lèi)化處理而不必承受不必要的痛苦,類(lèi)必須用認真選擇的受保護方法提供它內部實(shí)現的鉤子(hook)。
構造函數一定不能調用可重載的方法,無(wú)法直接地還是間接地。超類(lèi)構造函數會(huì )在子類(lèi)構造函數之前運行,所以子類(lèi)中的重載方法會(huì )在子類(lèi)構造函數運行之前被調用。如果重載方法依賴(lài)于由子類(lèi)構造函數執行的初始化,那么該方法將不會(huì )按期望的方式執行。

clone和readObject方法都不能調用可重載的方法,無(wú)論是直接的還是間接的。如果確定要用來(lái)繼承的類(lèi)實(shí)現Serializable,并且類(lèi)中有一個(gè)readResolve或writeReplace方法,那么必須使readResolve或writeReplace方法成為受保護的而不是私有的。一旦這些方法是私有的,它們就將被子類(lèi)悄然忽略。
對那些不是專(zhuān)門(mén)設計用于安全地實(shí)現子類(lèi)化并具有文檔說(shuō)明的類(lèi),禁止子類(lèi)化。
禁止子類(lèi)化的方法有兩種
Java語(yǔ)言為定義允許有多種實(shí)現的類(lèi)型提供了兩種機制:接口和抽象類(lèi)。
現有的類(lèi)可以很容易的被更新以實(shí)現新的接口。所有需要做的工作是增加還不存在的方法并在類(lèi)的聲明中增加一個(gè)implement語(yǔ)句。
接口是定義混合類(lèi)型(mixins)的理想選擇。mixin是這樣的類(lèi)型:除了它的“基本類(lèi)型(primary type)”外,類(lèi)還可以實(shí)現額外的類(lèi)型,以表明它提供了某些可選的功能。
接口允許非層次類(lèi)型框架的構造。對于組織某些事物,類(lèi)型層次是極好的選擇,但有些事物不能清楚地組織成嚴格的層次。
接口通過(guò)使用封裝類(lèi)方式,能夠獲得安全、強大的功能。如果使用抽象類(lèi)定義類(lèi)型,那么程序員在增加功能是,除了使用繼承外別無(wú)選擇,而且得到的類(lèi)與封裝類(lèi)相比功能更差、更脆弱。
盡管接口不允許方法實(shí)現,但使用接口定義類(lèi)型并不妨礙給程序員提供實(shí)現上的幫助??梢酝ㄟ^(guò)抽象的構架實(shí)現類(lèi)與希望輸出的所有重要的接口配合,從而將接口與抽象類(lèi)的優(yōu)點(diǎn)組合在一起。
使用抽象類(lèi)定義具有多個(gè)實(shí)現的類(lèi)型與使用接口相比有一個(gè)明顯優(yōu)勢:演進(jìn)抽象類(lèi)比演進(jìn)接口更容易。如果在以后的版本重,需要給抽象類(lèi)增加新方法,那么總可以增加一個(gè)包含正確的缺省實(shí)現的具體方法。此時(shí)所有現存的該抽象類(lèi)的實(shí)現都會(huì )具有這個(gè)新的方法。
嵌套類(lèi)(nested class)是一種定義在其他類(lèi)內部的類(lèi)。嵌套類(lèi)應該僅僅為包容它的類(lèi)而存在。
+ - 有四種類(lèi)型嵌套類(lèi):
靜態(tài)成員類(lèi)是最簡(jiǎn)單的嵌套類(lèi)。它最好被看做是普通的類(lèi)碰巧被聲明在其他的類(lèi)的內部。它對所有封閉類(lèi)中的成員有訪(fǎng)問(wèn)權限,甚至那些私有成員。靜態(tài)成員類(lèi)的一種通常用法是作為公共的輔助類(lèi),僅當和它的外部類(lèi)協(xié)作時(shí)才有意義。
如果聲明了一個(gè)不要求訪(fǎng)問(wèn)封閉實(shí)例的成員類(lèi),切記要在它的聲明里使用static修飾符,把成員類(lèi)變?yōu)殪o態(tài)的。 私有靜態(tài)成員類(lèi)的一般用法是用來(lái)表示它們的封閉類(lèi)對象的組件。
每一個(gè)非靜態(tài)的成員類(lèi)與它包含類(lèi)的封閉實(shí)例(enclosing instance)隱式地關(guān)聯(lián)。如果嵌套類(lèi)的實(shí)例可以在它的封閉類(lèi)實(shí)例之外單獨存在,那么嵌套類(lèi)不能成為非靜態(tài)成員類(lèi):創(chuàng )建沒(méi)有封閉實(shí)例的非靜態(tài)成員類(lèi)實(shí)例是不可能的。非靜態(tài)成員實(shí)例與它的封閉實(shí)例之間的關(guān)聯(lián)在前者被創(chuàng )建時(shí)即建立,在此之后它不能被修改。

過(guò)長(cháng)的匿名類(lèi)會(huì )傷害程序的可讀性。
//Typical use of an anonymous class
Arrays.sort(args, new Comparator() {
public int compare(Object o1,Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
}





每個(gè)類(lèi)實(shí)例本質(zhì)上是唯一的。
不關(guān)心類(lèi)是否提供了“邏輯意義的等同”(logical equality)測試。例如java.util.Random本來(lái)可以重載equals方法,用以檢查兩個(gè)Random實(shí)例是否會(huì )產(chǎn)生相同的隨機數序列,但設計者不認為客戶(hù)會(huì )需要或想要這個(gè)功能。這種情況下,使用從Object繼承的equals實(shí)現就夠了。
超類(lèi)已經(jīng)重載了equals,而從超類(lèi)繼承的行為適合該類(lèi)。
類(lèi)是私有的或包內私有(package-private)的,而且可以確定它的equals方法永遠不會(huì )被調用。





在重載equal方法時(shí)要重載hashCode方法。
不要使自己聰明過(guò)頭。把任何的同義形式考慮在比較的范圍內一般是糟糕的想法,例如File類(lèi)不應該與指向同一文件的符號鏈接進(jìn)行比較,實(shí)際上File類(lèi)也沒(méi)有這樣做。
不要設計依賴(lài)于不可靠資源的equals方法。
不要將equals聲明中的Object替換為其他類(lèi)型。程序員編寫(xiě)出形如下面所示的equals方法并不少見(jiàn),它會(huì )讓人摸不清頭腦:所設計方法為什么不能正確工作:
public boolean equals(Myclass o) {
...
}
問(wèn)題出在這個(gè)方法沒(méi)有重載(override)參數為Object類(lèi)型的Object.equals方法。而是過(guò)載(overload)了它。這在正常的equals方法中,又提供了一個(gè)“強類(lèi)型”的equals方法。
一定要在每一個(gè)重載了equals的類(lèi)中重載hashCode方法。不這樣做會(huì )違背Object.hashCode的一般約定,并導致你的類(lèi)與所有基于散列的集合一起作用時(shí)不能正常工作,這些集合包括HashMap、HashSet和Hashtable。
不重載hashCode方法違背了java.lang.Object的規范:相等的對象必須有相等的散列碼。兩個(gè)截然不同的實(shí)例根據類(lèi)的equals方法也許邏輯上是等同的,但對于Object類(lèi)的hashCode方法,它們就是兩個(gè)對象,僅此而已。因而對象的hashCode方法返回兩個(gè)看上去是隨機的數值,而不是約定中要求的相等的值。
好的hash函數傾向于為不相等的對象生成不相等的hash碼。理想的情況下,hash函數應該把所有不相等的實(shí)例的合理集合均一地分布到所有可能的hash值上去。達到理想狀態(tài)很難,但是下面有一種相對合適的方法


i.如果域是boolean型,計算(f?0:1)。
ii.如果域是byte型、char型、short型或int型,計算(int)f。
iii.如果域是long型,計算(int)(f^(f>>>32))。
iv.如果域是float型,計算Float.floattoIntBits(f)。
v.如果域是double型,計算Double.doubleToLongBits(f),然后如2.a.iii所示,對long型結果進(jìn)一步處理。
vi.如果域是對象引用,而且這個(gè)類(lèi)的equals方法又遞歸地調用了equals方法對域進(jìn)行比較,那么對這個(gè)域遞歸地調用hashCode方法。如果需要一種更復雜的比較方式,那么先為這個(gè)域計算出“范式表示”,然后在該“范式表示”上調用hashCode方法。如果域為null,則返回0。
vii.如果域是數組,則把每個(gè)元素作為分離的域對待。即遞歸地使用這些規則,為每個(gè)“主要元素”計算hash碼。然后用2.b所示方法復合這些值。


如果方法沒(méi)有對參數做檢查,會(huì )出現幾種情形。
對那些不被方法使用但會(huì )被保存以供使用的參數,檢查有效性尤為重要。
一種重要的例外是有效性檢查開(kāi)銷(xiāo)高,或者不切實(shí)際,而且這種有效性檢查在計算的過(guò)程中會(huì )被隱式地處理的情形。
總的來(lái)說(shuō),每次在設計方法或設計構造函數時(shí),要考慮它們的參數有什么限制。要在文檔中注釋出這些限制,并在方法體的開(kāi)頭通過(guò)顯示的檢查,對它們進(jìn)行強化。養成這樣的習慣是重要的,有效性檢查所需要的不多的工作會(huì )從它的好處中得到補償。
必須在客戶(hù)會(huì )使用一切手段破壞類(lèi)的約束的前提下,保護性地設計程序。


為了保護Period實(shí)例的內部細節免于這種攻擊,對構造函數的每一個(gè)可變參數使用保護性拷貝是必要的。


保護性拷貝要在參數的有效性檢查的前面,并且有效性檢測要在副本而不是初值上執行。











幾乎每次返回null而不是返回0長(cháng)度數組的方法時(shí),都需要這種多余地處理。返回null是易出錯的,因為寫(xiě)代碼的程序員可能會(huì )忘記設計處理返回null的特殊情形的代碼。



如果存在合適的接口類(lèi)型,那么參數、返回值、變量和域應該用接口類(lèi)型聲明。
應該養成下面這樣的程序習慣:
//Good - uses interface as type
List subscribers = new Vector();
而不要這樣做:
//Bad - uses class as type!
Vector subscribers = new Vector();
如果養成了使用接口作為類(lèi)型的習慣,程序就會(huì )有更好的擴展性。當希望轉換實(shí)現時(shí),需要做的全部工作就是改變構造函數中類(lèi)的名字。
聯(lián)系客服