Effective Java筆記一 創(chuàng)建和銷毀對(duì)象

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, OutputStreamjava.sql.Connection上的close()方法; java.util.Timercancel()方法.
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í)行.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惠豺,一起剝皮案震驚了整個(gè)濱河市辽故,隨后出現(xiàn)的幾起案子徒仓,更是在濱河造成了極大的恐慌,老刑警劉巖誊垢,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掉弛,死亡現(xiàn)場(chǎng)離奇詭異症见,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)殃饿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門谋作,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人乎芳,你說我怎么就攤上這事遵蚜。” “怎么了奈惑?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵吭净,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我携取,道長(zhǎng)攒钳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任雷滋,我火速辦了婚禮不撑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晤斩。我一直安慰自己焕檬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布澳泵。 她就那樣靜靜地躺著实愚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪兔辅。 梳的紋絲不亂的頭發(fā)上腊敲,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音维苔,去河邊找鬼碰辅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛介时,可吹牛的內(nèi)容都是我干的没宾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼沸柔,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼循衰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起褐澎,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤会钝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后工三,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顽素,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咽弦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胁出。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片型型。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖全蝶,靈堂內(nèi)的尸體忽然破棺而出闹蒜,到底是詐尸還是另有隱情,我是刑警寧澤抑淫,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布绷落,位于F島的核電站,受9級(jí)特大地震影響始苇,放射性物質(zhì)發(fā)生泄漏砌烁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一催式、第九天 我趴在偏房一處隱蔽的房頂上張望函喉。 院中可真熱鬧,春花似錦荣月、人聲如沸管呵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捐下。三九已至,卻和暖如春萌业,著一層夾襖步出監(jiān)牢的瞬間坷襟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工生年, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留婴程,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓晶框,卻偏偏與公主長(zhǎng)得像排抬,于是被迫代替她去往敵國(guó)和親懂从。 傳聞我的和親對(duì)象是個(gè)殘疾皇子授段,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容