第2章 創(chuàng)建和銷毀對(duì)象
第1條:考慮用靜態(tài)工廠方法代替構(gòu)造方法
靜態(tài)工廠方法與構(gòu)造方法的不同
優(yōu)點(diǎn):
- 靜態(tài)工廠方法有名稱。當(dāng)一個(gè)類需要多個(gè)帶有相同簽名的構(gòu)造器時(shí),就用靜態(tài)工廠方法代替構(gòu)造方法,并且慎重選擇名稱以便突出區(qū)別。
- 不用每次調(diào)用它們的時(shí)候都創(chuàng)建一個(gè)新對(duì)象。靜態(tài)工廠方法能夠?yàn)橹貜?fù)的調(diào)用返回相同的對(duì)象,有助于類總能?chē)?yán)格控制在某個(gè)時(shí)刻那些實(shí)例不該存在。
- 可以返回原返回類型的任何子類型的對(duì)象。有更大的靈活性;如APi可以返回對(duì)象,又同時(shí)不會(huì)使對(duì)象的類變成公有的,適用于基于接口的框架。公有的靜態(tài)工廠方法返回的對(duì)象的類不僅可以是非公有的,而且該類還可以隨著每次調(diào)用而發(fā)生變化,這取決于靜態(tài)工廠方法的參數(shù)值。
- 在創(chuàng)建參數(shù)化類型實(shí)例的時(shí)候,代碼更簡(jiǎn)潔。
缺點(diǎn):
- 類如果不含有公有的或者受保護(hù)的構(gòu)造器,就不能被子類化。
- 他們與其他的靜態(tài)方法實(shí)際上沒(méi)有任何區(qū)別。
第2條:遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮用構(gòu)造器
與構(gòu)造器相比,builder可以有多個(gè)可變的參數(shù)。builder模式十分靈活,可以利用單個(gè)builder構(gòu)造多個(gè)對(duì)象??傊绻惖臉?gòu)造器和靜態(tài)工廠中具有多個(gè)參數(shù),builder模式就是不錯(cuò)的選擇,特別是當(dāng)大多數(shù)參數(shù)是可選的時(shí)候。
第3條:用私有構(gòu)造器或者枚舉類型強(qiáng)化singleton屬性
第4條:用私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力
第5條:避免創(chuàng)建不必要的對(duì)象
一般來(lái)說(shuō),最好能重用對(duì)象而不是在每次需要的時(shí)候就創(chuàng)建一個(gè)相同功能的對(duì)象。
第6條:消除過(guò)期的對(duì)象引用
- 清空對(duì)象引用應(yīng)該是一種例外,而不是一種規(guī)范行為,消除過(guò)期引用最好的方法是讓包含該引用的變量結(jié)束其生命周期。
- stack類自己管理內(nèi)存,只要是類自己管理內(nèi)存,程序猿就應(yīng)該警惕內(nèi)存泄漏問(wèn)題。
- 內(nèi)存泄漏的另一種常見(jiàn)來(lái)源是緩存。只要在緩存之外存在對(duì)某個(gè)項(xiàng)的鍵的引用,該項(xiàng)就有意義,那么就可以用weakhashmap代表緩存。
- 內(nèi)存泄漏的第三個(gè)常見(jiàn)來(lái)源是監(jiān)聽(tīng)器和其他回調(diào)。確保回調(diào)立即被當(dāng)做垃圾回收的最佳方法是只保存它們的弱引用,例如,只將它們保持成WeakHashMap中的鍵。
第7條:避免使用終結(jié)方法
- 終結(jié)方法(finalizer)通常是不可預(yù)測(cè)的,也是很危險(xiǎn)的,一般情況下是不必要的。
- java語(yǔ)言規(guī)范不僅不保證終結(jié)方法會(huì)被及時(shí)執(zhí)行,而且根本不保證它們會(huì)被執(zhí)行。
- 使用終結(jié)方法有一個(gè)非常嚴(yán)重的(severe)性能損失
- 顯示的終結(jié)方法通常與try-finally結(jié)構(gòu)結(jié)合起來(lái)使用,以確保及時(shí)終止。(顯示終止方法的典型例子是InputStream,OutputStream和java.sql.Connection上的close方法,java.util.Timer上的cancel方法等)
- 總之,除非作為安全網(wǎng)(當(dāng)對(duì)象的所有者忘記調(diào)用前面段落中建議的顯示終止方法時(shí),終結(jié)方法可以充當(dāng)安全網(wǎng)),或者是為了終止非關(guān)鍵的本地資源,否則請(qǐng)不要使用終結(jié)方法。
第3章 對(duì)于所有對(duì)象都通用的方法
第8條:覆蓋equals時(shí)請(qǐng)遵守通用約定
- 類的每個(gè)實(shí)例本質(zhì)都是唯一的。對(duì)于代表活動(dòng)實(shí)體而不是值的類來(lái)說(shuō)確實(shí)如此,例如Thread。
- 不關(guān)心類是否提供了“邏輯相等”的測(cè)試功能。
- 超類已經(jīng)覆蓋了equals,從超類繼承過(guò)來(lái)的行為對(duì)于子類也是合適的。
- 類是私有的或是包級(jí)私有的,可以確定它的equals方法永遠(yuǎn)不會(huì)被調(diào)用。
在實(shí)現(xiàn)equals方法時(shí),必須遵守的通用約定
- 自反性。對(duì)于任何非null的引用值x,x.equals(x)必須返回true。
- 對(duì)稱性。對(duì)于任何非null的引用值x和y,當(dāng)且僅當(dāng)y.equals(x)時(shí),x.equals(y)必須返回true。
- 傳遞性。對(duì)于任何非null的引用值x,y和z,如果x.equals(y)返回true,并且y.equals(x)也返回true,那么x.equals(z)也必須返回true。
- 一致性。對(duì)于任何非null的引用值x和y,只要equals的比較操作在對(duì)象中所用的信息沒(méi)有被修改,多次調(diào)用x.equals(y)就會(huì)一致的返回true,或者一致的返回false。
- 非空性。意思是所有的對(duì)象都必須不等于null。
實(shí)現(xiàn)提高質(zhì)量equals方法的訣竅
- 使用==操作符檢查“參數(shù)是否為這個(gè)對(duì)象的引用”。
- 使用instanceof操作符檢查“參數(shù)是否為正確的類型”。
- 吧參數(shù)轉(zhuǎn)換成正確的類型。
- 對(duì)于該類中的每個(gè)“關(guān)鍵(significant)”域,檢查參數(shù)中的域是否與該對(duì)象中對(duì)應(yīng)的域相匹配。
- 當(dāng)編寫(xiě)完成了equals方法之后,應(yīng)該問(wèn)自己三個(gè)問(wèn)題:它是是否對(duì)稱的,傳遞的,一致的?
- 覆蓋equals是總要覆蓋hashCode。
- 不要企圖讓equals方法過(guò)于智能。
- 不要將equals聲明中的Object對(duì)象替換為其他類型。
第9條 覆蓋equals是總要覆蓋hashCode
- 相等的對(duì)象必須具有相等的散列碼(hash code)
- 不要試圖從散列碼計(jì)算中出掉一個(gè)對(duì)象的關(guān)鍵部分來(lái)提高性能。
第10條 始終要覆蓋toString
- 提供好的toString實(shí)現(xiàn)可以使類用起來(lái)更加舒適。
- toString方法應(yīng)該返回對(duì)象中包含的所有值得關(guān)注的信息。
- 指定toString返回值的格式也有不足之處:如果這個(gè)類已經(jīng)被廣泛使用,一旦指定格式,就必須始終如一地堅(jiān)持這種格式。
- 無(wú)論是否決定指定格式,都應(yīng)該在文檔中明確地表明你的意圖。如果要指定格式,則應(yīng)該嚴(yán)格地去做。
- 無(wú)論是否指定格式,都為toString返回值中包含的所有信息,提供一種編程式的訪問(wèn)途徑。
第11條 謹(jǐn)慎地覆蓋clone
第12條 考慮實(shí)現(xiàn)Comparable接口
第4章 類和接口
第13條:是類和成員的可訪問(wèn)性最小化
- 區(qū)別設(shè)計(jì)良好的模塊和設(shè)計(jì)不好的模塊,最重要的因素在于,這個(gè)模塊對(duì)于外部的其他模塊而言,是否隱藏其內(nèi)部數(shù)據(jù)和其他實(shí)現(xiàn)細(xì)節(jié)。(信息隱藏/封裝)
- 盡可能地使每個(gè)類或者成員不被外界訪問(wèn)
- 私有的(private)
- 包級(jí)私有的(package-private/default)
- 受保護(hù)的(protected)
- 公有的(public)
- 如果方法覆蓋了超類中的一個(gè)方法,子類中的訪問(wèn)級(jí)別就不允許低于超類的訪問(wèn)級(jí)別。接口中的所有方法都隱含著公有訪問(wèn)級(jí)別。
- 實(shí)例域決不能是公有的,包含公有可變域的類并不是線程安全的。
- 長(zhǎng)度非零的數(shù)組總是可變的,所以類具有公有的靜態(tài)final數(shù)組域,或者返回這種域的訪問(wèn)方法,這幾乎總是錯(cuò)誤的。
- 總之,應(yīng)該始終盡可能地降低可訪問(wèn)性。除了公有靜態(tài)final域的特殊情況之外,公有類都不應(yīng)該包含公有域。并且要確保公有靜態(tài)final域所引用的對(duì)象都是不可變的。
第14條:在公有類中使用訪問(wèn)方法而非公有域
- 如果類可以在它所在的包的外部進(jìn)行訪問(wèn),就提供訪問(wèn)方法,以保留將來(lái)改變?cè)擃惖膬?nèi)部表示法的靈活性。
- 如果類是包級(jí)私有的,或者是私有的嵌套類,直接暴露它的數(shù)據(jù)域并沒(méi)有本質(zhì)的錯(cuò)誤。
- 公有類永遠(yuǎn)都不應(yīng)該暴露可變域,雖然還是有問(wèn)題,但是讓公有類暴露不可變的域其危害比較小。但是,有時(shí)候會(huì)需要用包級(jí)私有的或者私有的嵌套類來(lái)暴露域,無(wú)論這個(gè)類是可變還是不可變的。
第15條:使可變性最小化
不可變類知識(shí)其實(shí)力不能被修改的類。每個(gè)實(shí)例中包含的所有信息都必須在創(chuàng)建實(shí)例的時(shí)候提供,并在對(duì)象的整個(gè)周期內(nèi)固定不變。
- 不要提供任何會(huì)修改對(duì)象狀態(tài)的方法(mutator)
- 保證類不會(huì)被擴(kuò)展
- 使所有的域都是final的
- 使所有的域都成為私有的
- 確保對(duì)于任何可變組件的互斥訪問(wèn)(如果類具有指向可變的域,則必須確保該類的客戶端無(wú)法獲得指向這些對(duì)象的引用)
- 創(chuàng)建并返回新的實(shí)例,而不是修改這個(gè)實(shí)例。大多數(shù)重要的不可變類都使用了這個(gè)模式,被稱為函數(shù)的做法。
- 不可變對(duì)象本質(zhì)上是線程安全的,它們不要求同步,可以被自由地共享。不僅可以共享不可變對(duì)象,甚至也可以共享它們的內(nèi)部信息,不需要保護(hù)性拷貝。缺點(diǎn):對(duì)于每個(gè)不同的值都需要一個(gè)單獨(dú)的對(duì)象。
- 總之,堅(jiān)決不用為每個(gè)get方法編寫(xiě)對(duì)應(yīng)的set方法,除非有很好的理由要讓類成為可變類,否則應(yīng)該是不可變的。如果類不能被做成不可變的,仍然應(yīng)改盡可能地限制它的可變性。
第16條:復(fù)合優(yōu)先于繼承
- 于方法調(diào)用不同,繼承打破了封裝性。
- 不用擴(kuò)展現(xiàn)有的類,而是在新的類中增加一個(gè)私有域,它引用現(xiàn)有類的一個(gè)實(shí)例。(復(fù)合/轉(zhuǎn)發(fā))這樣得到的類將會(huì)非常穩(wěn)固,它不依賴于現(xiàn)有類的實(shí)現(xiàn)細(xì)節(jié)。
- 只有當(dāng)子類真正是超類的子類型時(shí)(存在“is-a”關(guān)系),才適合用繼承。
第17條:要么為繼承設(shè)計(jì),并提供文檔說(shuō)明,要么就禁止繼承
第18條:接口優(yōu)于抽象類
- 接口和抽象類的區(qū)別:抽象類允許包含某些方法的實(shí)現(xiàn),但接口不允許。為了實(shí)現(xiàn)有抽象類定義的類型,類必須成為抽象類的一個(gè)子類,Java只允許單繼承,所以,抽象類作為類型定義受到了極大的限制。
- 現(xiàn)有的類可以很容易被更新,以實(shí)現(xiàn)新的接口。
- 接口的定義mixin(混合類型)的理想選擇。(mixin允許任選的功能可被混合到類型的主要功能中)
- 接口允許我們構(gòu)造非層次結(jié)構(gòu)的類型框架。
- 通過(guò)對(duì)你導(dǎo)出的每個(gè)重要的接口都提供一個(gè)抽象的骨架實(shí)現(xiàn)(skeletal implementation)類,把接口和抽象類的優(yōu)點(diǎn)都集合起來(lái)。接口的作用仍然是定義類型,但是骨架實(shí)現(xiàn)類接管了所有與接口實(shí)現(xiàn)相關(guān)的工作。
- 按照慣例,骨架實(shí)現(xiàn)被稱為AbstractInterface,這里的interface是指所實(shí)現(xiàn)的接口的名字。例如AbstractCollection,AbstractSet,AbstractList和AbstractMap等。
- 如果設(shè)計(jì)得當(dāng),骨架實(shí)現(xiàn)可以使程序員很容易提供他們自己的接口實(shí)現(xiàn)。
- 骨架實(shí)現(xiàn)為抽象類提供了實(shí)現(xiàn)上的幫助,但又不強(qiáng)加“抽象類被用作類型定義時(shí)”所特有的嚴(yán)格限制。
- 骨架實(shí)現(xiàn)類仍然能夠有助于接口的實(shí)現(xiàn)。(模擬多重繼承:實(shí)現(xiàn)這個(gè)接口的類可以把對(duì)于接口方法的調(diào)用,轉(zhuǎn)發(fā)到一個(gè)內(nèi)部私有類的實(shí)例上,這個(gè)內(nèi)部私有類擴(kuò)展了骨架的實(shí)現(xiàn))
- 編寫(xiě)骨架實(shí)現(xiàn)類相對(duì)比較簡(jiǎn)單,但是有點(diǎn)單調(diào)乏味。
- 因?yàn)楣羌軐?shí)現(xiàn)類是為繼承目的實(shí)現(xiàn)的,所以應(yīng)該遵循第17條中介紹的所有關(guān)于設(shè)計(jì)和文檔的指導(dǎo)原則。
- 骨架實(shí)現(xiàn)上有個(gè)小小的不同,就是簡(jiǎn)單實(shí)現(xiàn)(simple implementation),AbstractMap的SimpleEntry就是個(gè)例子。
- 使用抽象類來(lái)定義允許多個(gè)實(shí)現(xiàn)的類型,與使用接口相比有一個(gè)明顯的優(yōu)勢(shì):抽象類的演變比接口的演變要容易得多。
- 一般來(lái)說(shuō),要想在公有接口中增加方法,而不破壞實(shí)現(xiàn)這個(gè)接口的所有現(xiàn)有的類,這是不可能的。因此,設(shè)計(jì)公有的接口要非常謹(jǐn)慎,接口一旦被公開(kāi)發(fā)行,并且被廣泛實(shí)現(xiàn),在想改變這個(gè)接口幾乎是不可能的。
- 總之,接口通常是定義允許多個(gè)實(shí)現(xiàn)的類型的最佳途徑。這條規(guī)則有個(gè)例外,即但演變的容易性和靈活性和功能更為重要的時(shí)候。
第19條:接口只用于定義類型
當(dāng)類實(shí)現(xiàn)接口時(shí),接口就充當(dāng)可以引用這個(gè)類的實(shí)例的類型。常亮接口模式是對(duì)接口的不良使用。簡(jiǎn)而言之,接口應(yīng)該只被用來(lái)定義類型,它們不應(yīng)該被用來(lái)導(dǎo)出常亮。
第20條:類層次優(yōu)于標(biāo)簽類
- 類標(biāo)簽過(guò)于冗長(zhǎng),容易出錯(cuò),并且效率低下。為了將標(biāo)簽類轉(zhuǎn)變?yōu)轭悓哟?,首先要為?biāo)簽類中的每個(gè)方法都要定義一個(gè)包含抽象方法的抽象類,這每個(gè)方法的行為都依賴于標(biāo)簽值。
- 簡(jiǎn)而言之,標(biāo)簽類很少有適用的時(shí)候,當(dāng)你想要編寫(xiě)一個(gè)包含顯示標(biāo)簽域的類時(shí),應(yīng)該考慮一下,這個(gè)標(biāo)簽是否可以被取消,這個(gè)類是否可以用類層次來(lái)代替,當(dāng)你遇到一個(gè)包含標(biāo)簽域的現(xiàn)有類時(shí),就要考慮將它重構(gòu)到一個(gè)層次結(jié)構(gòu)中去。
第21條:用函數(shù)對(duì)象消失策略
函數(shù)指針的主要用途就是實(shí)現(xiàn)策略模式。為了在Java中實(shí)現(xiàn)這種模式,要聲明一個(gè)接口來(lái)表示該策略,并且為每個(gè)具體策略聲明一個(gè)實(shí)現(xiàn)了該接口的類。當(dāng)一個(gè)具體策略只被使用過(guò)一次時(shí),通常是使用匿名類來(lái)聲明和實(shí)例化這個(gè)具體策略類。當(dāng)一個(gè)具體策略是設(shè)計(jì)用來(lái)重復(fù)使用的時(shí)候,他的類通常就要被實(shí)現(xiàn)為私有的靜態(tài)成員類,并通過(guò)公有的靜態(tài)final域被導(dǎo)出,其類型為該策略接口。
第22條:優(yōu)先考慮靜態(tài)成員類
- 嵌套類是指被定義在另一個(gè)類的內(nèi)部的類。嵌套類分為四種:靜態(tài)成員類,非靜態(tài)成員類,匿名類和局部類。
- 如果一個(gè)嵌套類需要在單個(gè)方法之外仍然是可見(jiàn)的,或者它太長(zhǎng)了,不適合于放在方法內(nèi)部,就應(yīng)該使用成員類。如果成員類的每個(gè)實(shí)例都需要一個(gè)指向其外圍實(shí)例的引用,就要把成員類做成非靜態(tài)的;否則,就做成靜態(tài)的。架設(shè)這個(gè)嵌套類屬于一個(gè)方法的內(nèi)部,如果你只需要一個(gè)地方創(chuàng)建實(shí)例,并且已經(jīng)有一個(gè)預(yù)置的類型可以說(shuō)明這個(gè)類的特征,就要把它做成匿名類;否則,就做成局部類