本文主題是創(chuàng)建和銷(xiāo)毀對(duì)象房待,關(guān)注一下幾個(gè)問(wèn)題:
- 何時(shí)以及如何創(chuàng)建對(duì)象
- 何時(shí)以及如何避免創(chuàng)建對(duì)象
- 如何去報(bào)它們能夠適時(shí)銷(xiāo)毀
- 如何管理對(duì)象銷(xiāo)毀之前必須進(jìn)行的各種清理動(dòng)作
1.考慮使用靜態(tài)工廠方法代替構(gòu)造器(靜態(tài)工廠模式)
創(chuàng)建類(lèi)實(shí)例的方式有兩種:
- 公有的構(gòu)造器
- 公有的靜態(tài)工廠方法
靜態(tài)工廠方法
-
優(yōu)勢(shì)
- 靜態(tài)工廠方法與構(gòu)造器不同的第一大優(yōu)勢(shì)在于攀细,它們有名稱效扫,如果構(gòu)造器的參數(shù)本身沒(méi)有確切的描述返回的對(duì)象,那么適當(dāng)名稱的靜態(tài)工廠會(huì)更加合適
- 不必每次調(diào)用它們的時(shí)候都創(chuàng)建一個(gè)新對(duì)象俏脊,使得不可變對(duì)象可以使用預(yù)先構(gòu)建好的實(shí)例,利用緩存實(shí)例進(jìn)行復(fù)用,為重復(fù)的調(diào)用返回相同的對(duì)象怀喉,如果創(chuàng)建對(duì)象的代價(jià)很高,這個(gè)技術(shù)可以極大提升性能
- 可以返回類(lèi)型的任何子類(lèi)型的對(duì)象船响,在選擇返回對(duì)象時(shí)有更大的靈活性躬拢。
- 可以返回非公有對(duì)象,同時(shí)又不會(huì)使對(duì)象的類(lèi)變成公有的见间,隱藏實(shí)現(xiàn)類(lèi)
- 公有的靜態(tài)工廠方法所返回對(duì)象的類(lèi)不僅可以是非公有的聊闯,而且該類(lèi)可以對(duì)著每次調(diào)用而發(fā)生變化,取決于靜態(tài)工廠方法的參數(shù)值(工廠方法模式)
- 創(chuàng)建參數(shù)化類(lèi)型(泛型)實(shí)例時(shí)米诉,使代碼變得更加簡(jiǎn)潔
-
缺點(diǎn)
- 類(lèi)如果不含有公有或者受保護(hù)構(gòu)造器菱蔬,就不能被子類(lèi)化
- 與其他的靜態(tài)方法實(shí)際上沒(méi)有任何區(qū)別
-
靜態(tài)工廠方法命名規(guī)范
- valueOf
- 該方法返回的實(shí)例與它的參數(shù)具有相同的值,這樣的靜態(tài)工廠方法實(shí)際上是類(lèi)型轉(zhuǎn)換方法
- of
- getInstance
- 返回的實(shí)例通過(guò)方法的參數(shù)來(lái)描述,如果沒(méi)有參數(shù)拴泌,則返回唯一的單例
- newInstance
- 確保返回的實(shí)例都與其他實(shí)例不同
- getType
- newType
- valueOf
2.遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮用構(gòu)建器(Builder創(chuàng)建者模式)
靜態(tài)工廠和構(gòu)造器有個(gè)共同的局限性魏身,不能很好地?cái)U(kuò)展到大量可選參數(shù)。
處理有大量可選參數(shù)的構(gòu)造器的方式:
- 重疊構(gòu)造器
- JavaBeans 模式
- Builder 模式
重疊構(gòu)造器
提供第一個(gè)只有必要參數(shù)的構(gòu)造器蚪腐,第二個(gè)構(gòu)造器有一個(gè)可選參數(shù)箭昵,第三個(gè)有兩個(gè)可選參數(shù),以此類(lèi)推回季,最后一個(gè)構(gòu)造器包含所有可選參數(shù)家制。
public NutritionFact(int servingSize, int servings){}
public NutritionFact(int servingSize, int servings, int calories){}
public NutritionFact(int servingSize, int servings, int calories, int fat){}
public NutritionFact(int servingSize, int servings, int calories, int fat, int sodium){}
...
缺點(diǎn):重疊構(gòu)造器模式可行,但是當(dāng)有許多參數(shù)的時(shí)候泡一,客戶端代碼會(huì)很難編寫(xiě)和難以閱讀
JavaBeans 模式
另一種替代方法颤殴,JavaBeans 模式,調(diào)用一個(gè)無(wú)參構(gòu)造器來(lái)創(chuàng)建對(duì)象鼻忠,然后調(diào)用setter方法設(shè)置每個(gè)必要參數(shù)涵但,以及每個(gè)相關(guān)的可選參數(shù)。
NutritionFact cocoCola = new NutritionFact();
cocoCola.setServingSize(240);
cocoCola.setServings(8);
cocoCola.setCalories(100);
cocoCola.setSodium(35);
cocoCola.setCarbohydrate(27);
缺點(diǎn):
- 構(gòu)造過(guò)程被分到幾個(gè)調(diào)用中帖蔓,構(gòu)造過(guò)程 JavaBean 可能處于不一致的狀態(tài)矮瘟。類(lèi)無(wú)法僅僅通過(guò)校驗(yàn)構(gòu)造器參數(shù)的有效性來(lái)保證一致性
- JavaBean 模式阻止了把類(lèi)做成不可變的可能,需要確保它的線程安全
Builder 模式
不直接生成想要的對(duì)象讨阻,客戶端利用多有必要的參數(shù)調(diào)用構(gòu)造器(或靜態(tài)工廠)得到一個(gè)builder對(duì)象芥永,然后客戶端再builder 對(duì)象上調(diào)用類(lèi)似setter方法,來(lái)設(shè)置每個(gè)相關(guān)的可選參數(shù)钝吮,最后客戶端調(diào)用無(wú)參的build方法來(lái)生成不可變的對(duì)象埋涧,這個(gè)builder是類(lèi)的靜態(tài)成員類(lèi)。
public class NutritionFacts {
private final int calories = 0;
private final int fat = 0;
private final int sodium = 0;
// 靜態(tài)內(nèi)部類(lèi) Builder 對(duì)象
public static class Builder {
private int calories = 0;
private int fat = 0;
private int sodium = 0;
// setter方法返回當(dāng)前builder對(duì)象奇瘦,方便鏈?zhǔn)秸{(diào)用
public Builder setCalories(int val) {
calories = val;
}
public Builder setFat(int val) {
fat = val;
}
public Builder setSodium(int val) {
sodium = val;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
// 傳入 Builder 對(duì)象的構(gòu)造方法
public NutritionFacts(Builder builder) {
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
}
}
缺點(diǎn):需要額外開(kāi)銷(xiāo)
總結(jié)
如果類(lèi)的構(gòu)造器或者靜態(tài)工廠中具有多個(gè)參數(shù)棘催,設(shè)計(jì)這種類(lèi)時(shí),Builder 模式就是不錯(cuò)的選擇耳标。
3.用私有構(gòu)造器或者枚舉類(lèi)型強(qiáng)化Singleton屬性(單例模式)
Singleton 指僅僅被實(shí)例化一次的類(lèi)醇坝。
實(shí)現(xiàn)Singleton的方式有很多種
方式一
把構(gòu)造器保持為私有的,并導(dǎo)出公有的靜態(tài)成員次坡,并且靜態(tài)成員是個(gè)final的
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {...};
}
問(wèn)題:無(wú)法抵御通過(guò)反射調(diào)用私有構(gòu)造器的攻擊呼猪。
方案:可以修改構(gòu)造器,讓它在要求創(chuàng)建第二個(gè)實(shí)例的時(shí)候拋出異常砸琅。
方式二
方式二中宋距,公有成員不再是屬性,而是一個(gè)靜態(tài)方法getInstance
public class Elvis {
pvivate static final Elvis INSTANCE = new Elvis();
private Elvis() {...};
public static Elvis getInstance() {return INSTANCE;}
}
問(wèn)題:如果此類(lèi)實(shí)現(xiàn)了序列化症脂,序列化之后的結(jié)果都會(huì)創(chuàng)建一個(gè)新的實(shí)例谚赎。
方案:重寫(xiě)readResolve方法
private Object readResolve() {
return INSTANCE;
}
方式三
編寫(xiě)一個(gè)包含單個(gè)元素的枚舉類(lèi)型淫僻。
public enum Elvis {
INSTANCE;
}
與公有方法相近,但更加簡(jiǎn)潔壶唤,無(wú)償?shù)奶峁┝诵蛄谢瘷C(jī)制雳灵,并且防止序列到導(dǎo)致多次實(shí)例化,并且防止反射的攻擊闸盔。
總結(jié)
單元素的枚舉類(lèi)型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法悯辙。
4.通過(guò)私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力
工具類(lèi)不希望被實(shí)例化,實(shí)例對(duì)它沒(méi)有任何意義蕾殴。
在缺少顯示構(gòu)造器時(shí)笑撞,編譯器會(huì)自動(dòng)提供一個(gè)公有的岛啸,無(wú)參的缺省構(gòu)造器钓觉。
可通過(guò)創(chuàng)建私有構(gòu)造器,并構(gòu)造器中拋出異常坚踩,來(lái)避免實(shí)例化此類(lèi)荡灾。
5.避免創(chuàng)建不必要的對(duì)象
一般來(lái)說(shuō),最好能重用對(duì)象而不是在每次需要的時(shí)候就創(chuàng)建一個(gè)相同功能的新對(duì)象瞬铸。如果對(duì)象是不可變的批幌,那么它就應(yīng)該始終被重用。
舉例一
String s = new String("mystring") // 每次都會(huì)創(chuàng)建一個(gè)新的String實(shí)例
String s = "mystring" // 推薦嗓节,保證在同一臺(tái)虛擬機(jī)中運(yùn)行的代碼荧缘,只要包含相同的字符串字面常量,就會(huì)被重用
舉例二:不可變類(lèi)
對(duì)于不可變類(lèi)拦宣,優(yōu)先使用靜態(tài)工廠方法截粗,每次調(diào)用可以重用,避免創(chuàng)建不必要的對(duì)象鸵隧。
舉例三
通過(guò)靜態(tài)初始化器避免在每次調(diào)用方法時(shí)都會(huì)生成一些不必要的對(duì)象绸罗。
class Person {
static {
// 初始化整個(gè)類(lèi)需要用到的不可變可重用對(duì)象
}
public boolean isBaby() {
// 這里使用到一些不可變的對(duì)象,無(wú)需每次都創(chuàng)建豆瘫,把創(chuàng)建操作放到靜態(tài)初始化器中珊蟀,這里直接使用即可
}
}
缺點(diǎn):如果方法沒(méi)有被調(diào)用,那么初始化工作就沒(méi)有必要外驱,可以通過(guò)延遲初始化育灸,即把初始化工作放到方法初次調(diào)用時(shí)。
舉例四
自動(dòng)裝箱會(huì)創(chuàng)建出多余的對(duì)象昵宇。
sum 聲明為 Long 類(lèi)型磅崭,導(dǎo)致循環(huán)內(nèi)部構(gòu)造大量大于 Long 實(shí)例。
Long sum = 0;
for (long i = 0;i < Interger.MAX_VALUE; i ++) {
sum += i;
}
要優(yōu)先使用基本類(lèi)型而不是裝箱基本類(lèi)型趟薄,要當(dāng)心無(wú)意識(shí)的自動(dòng)裝箱绽诚。
舉例五
通過(guò)維護(hù)自己的對(duì)象池來(lái)避免創(chuàng)建對(duì)象并不會(huì)一種好的做法,除非池中的對(duì)象是非常重量級(jí)的,現(xiàn)代JVM實(shí)現(xiàn)具有高度優(yōu)化的垃圾回收器恩够,其性能很容易就超過(guò)輕量級(jí)對(duì)象池的性能卒落。
6.消除過(guò)期的對(duì)象引用
- 只要類(lèi)自己管理內(nèi)存,程序就應(yīng)該警惕內(nèi)存泄漏問(wèn)題
- 內(nèi)存泄漏另一個(gè)場(chǎng)景來(lái)源是緩存
- 另一個(gè)場(chǎng)景是監(jiān)聽(tīng)器和其他回調(diào)蜂桶,如果注冊(cè)了回調(diào)儡毕,卻沒(méi)有顯式地取消注冊(cè),那么會(huì)產(chǎn)生內(nèi)存泄漏扑媚,確毖澹回調(diào)立即被當(dāng)做垃圾回收的最佳方法是只保持它們的弱引用。
7.避免使用終結(jié)方法
- 介紹
- 終結(jié)方法(finalizer)通常是不可預(yù)測(cè)的疆股,也是很危險(xiǎn)的费坊,一般情況下是不必要的
- 使用終結(jié)方法導(dǎo)致行為不穩(wěn)定,降低性能旬痹,以及可移植性問(wèn)題
- C++的析構(gòu)函數(shù)可以被用來(lái)回收其他的非內(nèi)存資源附井,Java 中,一般用try-finally塊來(lái)完成類(lèi)似工作
- 終結(jié)方法的缺點(diǎn)在于不能保證被及時(shí)執(zhí)行两残,JVM會(huì)延遲執(zhí)行終結(jié)方法永毅,所以不要用來(lái)關(guān)閉已經(jīng)打開(kāi)的文件,程序不能依賴終結(jié)方法被執(zhí)行的時(shí)間點(diǎn)
- 不應(yīng)該依賴終結(jié)方法來(lái)更新重要的持久狀態(tài)
- System.gc 和 System.runFinalization 增加了終結(jié)方法執(zhí)行的機(jī)會(huì)人弓,但不能保證終結(jié)方法一定會(huì)被執(zhí)行
- System.runFinalizersOnExit 可以保證終結(jié)方法被執(zhí)行沼死,當(dāng)然此方法已被廢棄
- 如果被捕獲的異常在終結(jié)方法中被拋出,那么這種異常會(huì)被忽略
- 使用終結(jié)方法有非常嚴(yán)重的性能損失崔赌,即使什么也不做
- 終止資源(文件或線程資源)
- 顯示提供一個(gè)終止方法意蛀,在實(shí)例不再需要時(shí),調(diào)用此方法
- 通常與try-finally結(jié)構(gòu)結(jié)合峰鄙,在finally中顯式調(diào)用終止方法