Effective Java筆記一 創(chuàng)建和銷毀對(duì)象
- 第1條 考慮用靜態(tài)工廠方法代替構(gòu)造器
- 第2條 遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮用構(gòu)建器
- 第3條 用私有構(gòu)造器或者枚舉類型強(qiáng)化Singleton屬性
- 第4條 通過私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力
- 第5條 避免創(chuàng)建不必要的對(duì)象
- 第6條 消除過期的對(duì)象引用
- 第7條 避免使用終結(jié)方法
第1條 考慮用靜態(tài)工廠方法代替構(gòu)造器
對(duì)于類而言, 最常用的獲取實(shí)例的方法就是提供一個(gè)公有的構(gòu)造器, 還有一種方法, 就是提供一個(gè)公有的靜態(tài)工廠方法(static factory method), 返回類的實(shí)例.
(注意此處的靜態(tài)工廠方法與設(shè)計(jì)模式中的工廠方法模式不同.)
提供靜態(tài)工廠方法而不是公有構(gòu)造, 這樣做有幾大優(yōu)勢(shì):
- 靜態(tài)工廠方法有名稱. 可以更確切地描述正被返回的對(duì)象.
當(dāng)一個(gè)類需要多個(gè)帶有相同簽名的構(gòu)造器時(shí), 可以用靜態(tài)工廠方法, 并且慎重地選擇名稱以便突出它們之間的區(qū)別. - 不必在每次調(diào)用它們的時(shí)候都創(chuàng)建一個(gè)新對(duì)象. 可以重復(fù)利用實(shí)例. 如果程序經(jīng)常請(qǐng)求創(chuàng)建相同的對(duì)象, 并且創(chuàng)建對(duì)象的代價(jià)很高, 這項(xiàng)改動(dòng)可以提升性能. (不可變類, 單例, 枚舉).
-
可以返回原類型的子類型對(duì)象. 適用于基于接口的框架, 可以隱藏實(shí)現(xiàn)類API, 也可以根據(jù)參數(shù)返回不同的子類型.
由于接口不能有靜態(tài)方法, 因此按照慣例, 接口Type的靜態(tài)工廠方法被放在一個(gè)名為Types的不可實(shí)例化的類中.
(Java的java.util.Collections). 服務(wù)提供者框架(Service Provider Framework, 如JDBC)的基礎(chǔ), 從實(shí)現(xiàn)中解耦. - 在創(chuàng)建參數(shù)化類型實(shí)例的時(shí)候, 使代碼更簡(jiǎn)潔.
靜態(tài)工廠方法的缺點(diǎn):
- 類如果不含public或者protected的構(gòu)造器, 就不能被子類化. 對(duì)于公有的靜態(tài)工廠方法所返回的非公有類, 也同樣如此.
- 靜態(tài)工廠方法與其他的靜態(tài)方法沒有區(qū)別. 在API文檔中沒有明確標(biāo)識(shí)出來. 可以使用一些慣用的名稱來彌補(bǔ)這一劣勢(shì):
-
valueOf()
: 類型轉(zhuǎn)換方法, 返回的實(shí)例與參數(shù)具有相同的值. -
of()
: valueOf()的一種更簡(jiǎn)潔的替代. -
getInstance()
: 返回的實(shí)例通過參數(shù)來描述, 對(duì)于單例來說, 該方法沒有參數(shù), 返回唯一的實(shí)例. -
newInstance()
: 像getInstance()一樣, 但newInstance()能確保返回的每個(gè)實(shí)例都與其他實(shí)例不同. -
getType()
: 像getInstance()一樣, Type表示返回的對(duì)象類型, 在工廠方法處于不同的類中的時(shí)候使用. -
newType()
: 和newInstance()一樣, Type表示返回類型, 在工廠方法處于不同的類中的時(shí)候使用.
-
第2條 遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮用構(gòu)建器
靜態(tài)工廠和構(gòu)造器有一個(gè)共同的局限性: 它們都不能很好地?cái)U(kuò)展到大量的可選參數(shù).
重載多個(gè)構(gòu)造器方法可行, 但是當(dāng)有許多參數(shù)的時(shí)候, 代碼會(huì)很難寫難讀.
第二種替代方法是JavaBeans模式, 即一個(gè)無參數(shù)構(gòu)造來創(chuàng)建對(duì)象, 然后調(diào)用setter方法來設(shè)置每個(gè)參數(shù). 這種模式也有嚴(yán)重的缺點(diǎn), 因?yàn)闃?gòu)造過程被分到了幾個(gè)調(diào)用中, 在構(gòu)造過程中JavaBean可能處于不一致的狀態(tài).
類無法通過檢驗(yàn)構(gòu)造器參數(shù)的有效性來保證一致性. 另一點(diǎn)是這種模式阻止了把類做成不可變的可能.
第三種方法就是Builder模式. 不直接生成想要的對(duì)象, 而是利用必要參數(shù)調(diào)用構(gòu)造器(或者靜態(tài)工廠)得到一個(gè)builder對(duì)象, 然后在builder對(duì)象上調(diào)用類似setter的方法, 來設(shè)置可選參數(shù), 最后調(diào)用無參的build()
方法來生成不可變的對(duì)象.
這個(gè)Builder是它構(gòu)建的類的靜態(tài)成員類.
Builder的setter方法返回Builder本身, 可以鏈?zhǔn)讲僮?
Builder模式的優(yōu)勢(shì): 可讀性增強(qiáng); 可以有多個(gè)可變參數(shù); 易于做參數(shù)檢查和構(gòu)造約束檢查; 比JavaBeans更加安全; 靈活性: 可以利用單個(gè)builder構(gòu)建多個(gè)對(duì)象, 可以自動(dòng)填充某些域, 比如自增序列號(hào).
Builder模式的不足: 為了創(chuàng)建對(duì)象必須先創(chuàng)建Builder, 在某些十分注重性能的情況下, 可能就成了問題; Builder模式較冗長(zhǎng), 因此只有參數(shù)很多時(shí)才使用.
第3條 用私有構(gòu)造器或者枚舉類型強(qiáng)化Singleton屬性
Singleton(單例)
指僅僅被實(shí)例化一次的類. 通常用來代表那些本質(zhì)上唯一的系統(tǒng)組件.
使類成為Singleton會(huì)使得它的客戶端代碼測(cè)試變得困難, 因?yàn)闊o法給它替換模擬實(shí)現(xiàn), 除非它實(shí)現(xiàn)了一個(gè)充當(dāng)其類型的接口.
單例的實(shí)現(xiàn): 私有構(gòu)造方法, 類中保留一個(gè)字段實(shí)例(static, final), 用public直接公開字段或者用一個(gè)public static的getInstance()
方法返回該字段.
為了使單例實(shí)現(xiàn)序列化(Serializable
), 僅僅在聲明中加上implements Serializable
是不夠的, 為了維護(hù)并保證單例, 必須聲明所有實(shí)例域都是transient
的, 并提供一個(gè)readResolve()
方法, 返回單例的實(shí)例. 否則每次反序列化一個(gè)實(shí)例時(shí), 都會(huì)創(chuàng)建一個(gè)新的實(shí)例.
從Java 1.5起, 可以使用枚舉來實(shí)現(xiàn)單例: 只需要編寫一個(gè)包含單個(gè)元素的枚舉類型.
這種方法無償?shù)靥峁┝诵蛄谢瘷C(jī)制, 絕對(duì)防止多次實(shí)例化.
第4條 通過私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力
只包含靜態(tài)方法和靜態(tài)域的類名聲不太好, 因?yàn)橛行┤藭?huì)濫用它們來編寫過程化的程序. 盡管如此, 它們確實(shí)也有特有的用處, 比如:
java.lang.Math
, java.util.Arrays
把基本類型的值或數(shù)組類型上的相關(guān)方法組織起來; java.util.Collections
把實(shí)現(xiàn)特定接口的對(duì)象上的靜態(tài)方法組織起來; 還可以利用這種類把final類上的方法組織起來, 以取代擴(kuò)展該類的做法.
這種工具類(utility class)不希望被實(shí)例化, 然而在缺少顯式構(gòu)造器的情況下, 系統(tǒng)會(huì)提供默認(rèn)構(gòu)造器, 可能會(huì)造成這些類被無意識(shí)地實(shí)例化.
通過做成抽象類來強(qiáng)制該類不可被實(shí)例化, 這是行不通的, 因?yàn)榭赡軙?huì)造成"這個(gè)類是用來被繼承的"的誤解, 而繼承它的子類又可以被實(shí)例化.
所以只要讓這個(gè)類包含一個(gè)私有的構(gòu)造器, 它就不能被實(shí)例化了. 進(jìn)一步地, 可以在這個(gè)私有構(gòu)造器中拋出異常.
這種做法還會(huì)導(dǎo)致這個(gè)類不能被子類化, 因?yàn)樽宇悩?gòu)造器必須顯式或隱式地調(diào)用super構(gòu)造器. 在這種情況下, 子類就沒有可訪問的超類構(gòu)造器可調(diào)用了.
第5條 避免創(chuàng)建不必要的對(duì)象
一般來說, 最好能重用對(duì)象而不是每次需要的時(shí)候創(chuàng)建一個(gè)相同功能的新對(duì)象. 如果對(duì)象是不可變的(immutable), 它就始終可以被重用.
比如應(yīng)該用:
String s = "stringette";
而不是:
String s = new String("stringette"); // Don't do this
包含相同字符串的字面常量對(duì)象是會(huì)被重用的.
對(duì)于同時(shí)提供了靜態(tài)工廠方法和構(gòu)造方法的不可變類, 通臣搜颍可以使用靜態(tài)工廠方法而不是構(gòu)造器, 以避免創(chuàng)建不必要的對(duì)象.
比如Boolean.valueOf()
.
除了重用不可變對(duì)象以外, 也可以重用那些已知不會(huì)被修改的可變對(duì)象. 比如把一個(gè)方法中需要用到的不變的數(shù)據(jù)保存成常量對(duì)象(static final
), 只在初始化的時(shí)候創(chuàng)建一次(用static塊
), 這樣就不用每次調(diào)用方法都重復(fù)創(chuàng)建.
如果該方法永遠(yuǎn)不會(huì)調(diào)用, 那也不需要初始化相關(guān)的字段, 可以通過延遲初始化(lazily initializing)把這些對(duì)象的初始化放到方法第一次被調(diào)用的時(shí)候. (但是不建議這樣做, 沒有性能的顯著提高, 并且會(huì)使方法看起來復(fù)雜.)
前面的例子中, 所討論的對(duì)象顯然是能夠被重用的, 因?yàn)樗鼈儽怀跏蓟蟛粫?huì)再改變. 其他有些情形則并不總是這么明顯了. (適配器(adapter)模式, Map的接口keySet()方法返回同樣的Set實(shí)例).
Java 1.5中加入了自動(dòng)裝箱(autoboxing), 會(huì)創(chuàng)建對(duì)象. 所以程序中優(yōu)先使用基本類型而不是裝箱基本類型, 要當(dāng)心無意識(shí)的自動(dòng)裝箱.
小對(duì)象的構(gòu)造器只做很少量的顯式工作, 創(chuàng)建和回收都是很廉價(jià)的, 所以通過創(chuàng)建附加的對(duì)象提升程序的清晰簡(jiǎn)潔性也是好事.
通過維護(hù)自己的對(duì)象池(object pool)來避免創(chuàng)建對(duì)象并不是一種好的做法(代碼, 內(nèi)存), 除非池中的對(duì)象是非常重量級(jí)的. 正確使用的典型: 數(shù)據(jù)庫(kù)連接池.
第6條 消除過期的對(duì)象引用
一個(gè)內(nèi)存泄露的例子: 一個(gè)用數(shù)組實(shí)現(xiàn)的Stack, 依靠size標(biāo)記來管理?xiàng)5纳疃? 但是這樣從棧中彈出來的過期對(duì)象并沒有被釋放.
稱內(nèi)存泄露為"無意識(shí)的對(duì)象保持(unintentional object retention)"更為恰當(dāng).
修復(fù)方法: 一旦對(duì)象引用已經(jīng)過期, 只需清空這些引用即可.
清空對(duì)象引用應(yīng)該是一種例外, 而不是一種規(guī)范行為. 消除過期引用最好的方法是讓包含該引用的變量結(jié)束其生命周期. 如果你是在最緊湊的作用域范圍內(nèi)定義變量, 這種情形就會(huì)自然發(fā)生.
一般而言, 只要類是自己管理內(nèi)存, 程序員就應(yīng)該警惕內(nèi)存泄露問題. 一旦元素被釋放掉, 則該元素中包含的任何對(duì)象引用都應(yīng)該被清空.
內(nèi)存泄露的另一個(gè)常見來源是緩存. 這個(gè)問題有這幾種可能的解決方案:
- 1.緩存項(xiàng)的生命周期由該鍵的外部引用決定 ->
WeakHashMap
; - 2.緩存項(xiàng)的生命周期是否有意義并不是很容易確定 -> 隨著時(shí)間的推移或者新增項(xiàng)的時(shí)候刪除沒用的項(xiàng).
內(nèi)存泄露的第三個(gè)常見來源是監(jiān)聽器和其他回調(diào).
如果你實(shí)現(xiàn)了一個(gè)API, 客戶端注冊(cè)了回調(diào)卻沒有注銷, 就會(huì)積聚對(duì)象.
API端可以只保存對(duì)象的弱引用來確惫ひ福回調(diào)對(duì)象生命周期結(jié)束后會(huì)被垃圾回收.
第7條 避免使用終結(jié)方法
終結(jié)方法(finalizer)通常是不可預(yù)測(cè)的, 也是很危險(xiǎn)的, 一般情況下是不必要的.
使用終結(jié)方法會(huì)導(dǎo)致行為不穩(wěn)定, 降低性能, 以及可移植性問題.
不要把finalizer當(dāng)成是C++中的析構(gòu)器(destructors)的對(duì)應(yīng)物.
在Java中, 當(dāng)一個(gè)對(duì)象變得不可到達(dá)的時(shí)候, 垃圾回收器會(huì)回收與該對(duì)象相關(guān)聯(lián)的存儲(chǔ)空間.
C++的析構(gòu)器也可以用來回收其他的非內(nèi)存資源, 而在Java中, 一般用try-finally塊來完成類似的工作.
終結(jié)方法的缺點(diǎn)在于不能保證會(huì)被及時(shí)地執(zhí)行. 從一個(gè)對(duì)象變得不可到達(dá)開始, 到它的終結(jié)方法被執(zhí)行, 所花費(fèi)的時(shí)間是任意長(zhǎng)的. JVM會(huì)延遲執(zhí)行終結(jié)方法.
及時(shí)地執(zhí)行終結(jié)方法正是垃圾回收算法的一個(gè)主要功能. 這種算法在不同的JVM上不同.
Java語言規(guī)范不僅不保證終結(jié)方法會(huì)被及時(shí)地執(zhí)行, 而且根本就不保證它們會(huì)被執(zhí)行. 所以不應(yīng)該依賴于終結(jié)方法來更新重要的持久狀態(tài).
不要被System.gc()
和System.runFinalization()
這兩個(gè)方法所迷惑, 它們確實(shí)增加了終結(jié)方法被執(zhí)行的機(jī)會(huì), 但是它們并不保證終結(jié)方法一定會(huì)被執(zhí)行.
如果未捕獲的異常在終結(jié)過程中被拋出來, 那么這種異常可以被忽略, 而且該對(duì)象的終結(jié)過程也會(huì)終止.
使用終結(jié)方法有一個(gè)嚴(yán)重的性能損失.
如果類的對(duì)象中封裝的資源(例如文件或線程)確實(shí)需要終止, 應(yīng)該怎么做才能不用編寫終結(jié)方法呢? 只需提供一個(gè)顯式的終止方法. 并要求該類的客戶端在每個(gè)實(shí)例不再有用的時(shí)候調(diào)用這個(gè)方法. 注意, 該實(shí)例必須記錄下自己是否已經(jīng)被終止了, 如果被終止之后再被調(diào)用, 要拋出異常.
例子: InputStream
, OutputStream
和java.sql.Connection
上的close()
方法; java.util.Timer
的cancel()
方法.
Image.flush()
會(huì)釋放實(shí)例相關(guān)資源, 但該實(shí)例仍處于可用的狀態(tài), 如果有必要會(huì)重新分配資源.
顯式的終止方法通常與try-finally塊結(jié)合使用, 以確保及時(shí)終止.
終結(jié)方法的好處, 它有兩種合法用途:
- 當(dāng)顯式終止方法被忘記調(diào)用時(shí), 終結(jié)方法可以充當(dāng)安全網(wǎng)(safety net). 但是如果終結(jié)方法發(fā)現(xiàn)資源還未被終止, 應(yīng)該記錄日志警告, 這表示客戶端代碼中的bug.
- 對(duì)象的本地對(duì)等體(native peer), 垃圾回收器不會(huì)知道它, 當(dāng)它的Java對(duì)等體被回收的時(shí)候, 它不會(huì)被回收. 如果本地對(duì)等體擁有必須被及時(shí)終止的資源, 那么該類就應(yīng)該有一個(gè)顯式的終止方法, 如前, 可以是本地方法或者它也可以調(diào)用本地方法; 如果本地對(duì)等體并不擁有關(guān)鍵資源, 終結(jié)方法是執(zhí)行這項(xiàng)任務(wù)最合適的工具.
注意, 終結(jié)方法鏈(finalizer chaining)并不會(huì)自動(dòng)執(zhí)行. 子類覆蓋終結(jié)方法時(shí), 必須手動(dòng)調(diào)用超類的終結(jié)方法. try中終結(jié)子類, finally中終結(jié)超類.
為了避免忘記調(diào)用超類的終結(jié)方法, 還有一種寫法, 是在子類中寫一個(gè)匿名的類, 該匿名類的單個(gè)實(shí)例被稱為終結(jié)方法守衛(wèi)者(finalizer guardian), 當(dāng)守衛(wèi)者被終結(jié)的時(shí)候, 它執(zhí)行外圍實(shí)例的終結(jié)行為. 這樣外圍類并沒有覆蓋超類的終結(jié)方法, 保證了超類的終結(jié)方法一定會(huì)被執(zhí)行.