第二章 創(chuàng)建和銷毀對(duì)象
何時(shí)以及如何創(chuàng)建對(duì)象裆蒸,何時(shí)以及如何避免創(chuàng)建對(duì)象勾怒,如何確保他們能夠適時(shí)地銷毀胞此,以及如何管理對(duì)象銷毀之前必須進(jìn)行的各種清理動(dòng)作。
1.考慮用靜態(tài)工廠方法代替構(gòu)造器
一般在某處獲取一個(gè)類的實(shí)例最常用的方法是提供一個(gè)共有的構(gòu)造器涩金,還有一種方法,就是提供一個(gè)共有的靜態(tài)工廠(static factory method)暇仲,他只是一個(gè)返回類的實(shí)例的靜態(tài)方法步做。
例:
public static Boolean valueOf(boolean b){
return b ? Boolean.TRUE:Boolean.FALSE;
}
注意,靜態(tài)工廠方法與設(shè)計(jì)模式中的工廠方法模式不同奈附。類可以通過靜態(tài)工廠方法來提供給它的客戶端全度,而不是通過構(gòu)造器,提供靜態(tài)工廠方法而不是公有的構(gòu)造器斥滤,這樣做具有幾大優(yōu)勢(shì):
優(yōu)勢(shì)
-
靜態(tài)工廠方法将鸵,它們有名稱
- 例如構(gòu)造器BIgInteger(int,int,Random)返回的BigInteter可能為素?cái)?shù)勉盅,如果用名為BigInteger.probablePrime的靜態(tài)工廠方法來表示,顯然更為清楚顶掉。
-
不必在每次調(diào)用它們的時(shí)候都創(chuàng)建一個(gè)新的對(duì)象
- 這使得不可變類可以使用預(yù)先構(gòu)建好的實(shí)例草娜,或者將構(gòu)建好的實(shí)例緩存起來,進(jìn)行重復(fù)利用痒筒,從而避免常見不必要的重復(fù)對(duì)象宰闰,因?yàn)槌绦蚪?jīng)常請(qǐng)求創(chuàng)建相同的對(duì)象,那么創(chuàng)建對(duì)象的代價(jià)會(huì)很高簿透。Boolean.valueOf(boolean)方法說明了這項(xiàng)技術(shù)移袍。靜態(tài)工廠方法也經(jīng)常用于實(shí)現(xiàn)單例模式。
- 它們可以返回原返回類型的任何子類型的對(duì)象
靈活的靜態(tài)工廠方法構(gòu)成了服務(wù)提供者框架(Service Provider FrameWork)的基礎(chǔ)老充,例如JDBC葡盗。
服務(wù)提供者框架:多個(gè)服務(wù)提供者實(shí)現(xiàn)一個(gè)服務(wù),系統(tǒng)為服務(wù)提供者的客戶端提供多個(gè)實(shí)現(xiàn)啡浊,并把他們從多個(gè)實(shí)現(xiàn)總解耦出來觅够。三個(gè)組件:服務(wù)接口,這是服務(wù)提供者實(shí)現(xiàn)的;提供者注冊(cè)API虫啥,這是系統(tǒng)用來注冊(cè)實(shí)現(xiàn)蔚约,讓客戶端訪問他們的;服務(wù)訪問API涂籽,是客戶端用來獲取服務(wù)的實(shí)例的苹祟。
對(duì)于JDBC,Connection就是它的服務(wù)接口评雌,DriverManager.registerDriver是提供者注冊(cè)API树枫,DriverManager.getConnection是服務(wù)訪問API,Driver就是服務(wù)提供者接口景东。
例砂轻,下列簡單的實(shí)現(xiàn)包含了一個(gè)服務(wù)提供者接口和一個(gè)默認(rèn)提供者:
/**
* Created by Newtrek on 2017/10/31.
* 服務(wù)提供者接口
*/
public interface Provider {
// 提供服務(wù)實(shí)例,用服務(wù)接口返回
Service newService();
}
/**
* Created by Newtrek on 2017/10/31.
* 服務(wù)接口
*/
public interface Service {
// TODO: 2017/10/31 服務(wù)特有的方法 寫在這兒
}
/**
* Created by Newtrek on 2017/10/31.
*/
public class Services {
// 構(gòu)造保護(hù)
private Services(){}
// provider映射表斤吐,保存注冊(cè)的Provider
private static final Map<String ,Provider> providers=new ConcurrentHashMap<>();
// 默認(rèn)provider的名字
public static final String DEFAULT_PROVIDER_NAME="<def>";
// 注冊(cè)默認(rèn)的Provider
public static void registerDefaultProvider(Provider p){
registerProvider(DEFAULT_PROVIDER_NAME,p);
}
// 注冊(cè)provider
public static void registerProvider(String name,Provider p){
providers.put(name,p);
}
/**
* 靜態(tài)工廠方法返回Service實(shí)例
*/
public static Service newInstance(){
return newInstance(DEFAULT_PROVIDER_NAME);
}
public static Service newInstance(String name){
Provider p = providers.get(name);
if (p==null){
throw new IllegalArgumentException("No provider registered with name:"+name);
}
return p.newService();
}
}
- 在創(chuàng)建參數(shù)化類型實(shí)例的時(shí)候搔涝,它們是代碼變得更加簡潔
例:假設(shè)HashMap提供了這個(gè)靜態(tài)工廠
public static <K,V> HashMap<K,V> newInstance(){
return new HashMap<K,V>();
}
那么就可以用下面簡潔的代碼獲取實(shí)例了。
Map<String,List<String>> m=HashMap.newInstance();
把這些方法放在自己的工具類中是很實(shí)用的和措。不過現(xiàn)在java7,java8已經(jīng)實(shí)現(xiàn)了HashMap構(gòu)造的類型參數(shù)推測(cè)
缺點(diǎn)
- 類如果不含公有的或者受保護(hù)的構(gòu)造器庄呈,就不能被子類化
-
它們與其他的靜態(tài)方法實(shí)際上沒有任何區(qū)別
- 在API文檔中,它們沒有像構(gòu)造器那樣在API文檔中明確標(biāo)識(shí)出來,因?yàn)榕哨澹瑢?duì)于提供了靜態(tài)工廠方法二不是構(gòu)造器的類來說诬留,要想查明如何實(shí)例化一個(gè)類,這是非常困難的∥亩遥可以通過在類或者接口注釋中關(guān)注靜態(tài)工廠盒刚,并遵守標(biāo)準(zhǔn)的命名習(xí)慣,可以彌補(bǔ)這一劣勢(shì)绿贞。下面是靜態(tài)工廠方法的一些慣用名稱因块。
- valueOf:實(shí)際上是類型轉(zhuǎn)換方法。
- of:valueOf的一種更為簡潔的代替
- getInstance:返回的實(shí)例是通過方法的參數(shù)來描述的樟蠕,但是不能夠說與參數(shù)具有同樣的值贮聂。對(duì)于Singleton來說,該方法沒有參數(shù)寨辩,并返回唯一的實(shí)例吓懈。
- newInstance:像getInstance一樣,但newInstance能夠確保返回的每個(gè)實(shí)例都與所有其它實(shí)例不同靡狞。
- getType:像getInstance一樣耻警,但是在工廠方法處于不同的類中的時(shí)候使用。Type表示工廠方法所返回的對(duì)象類型甸怕。
- newType:像newInstance一樣甘穿,但是在工廠方法處于不同的類中的時(shí)候使用,Type表示工廠方法所返回的對(duì)象類型。
- 在API文檔中,它們沒有像構(gòu)造器那樣在API文檔中明確標(biāo)識(shí)出來,因?yàn)榕哨澹瑢?duì)于提供了靜態(tài)工廠方法二不是構(gòu)造器的類來說诬留,要想查明如何實(shí)例化一個(gè)類,這是非常困難的∥亩遥可以通過在類或者接口注釋中關(guān)注靜態(tài)工廠盒刚,并遵守標(biāo)準(zhǔn)的命名習(xí)慣,可以彌補(bǔ)這一劣勢(shì)绿贞。下面是靜態(tài)工廠方法的一些慣用名稱因块。
簡而言之梢杭,靜態(tài)工廠方法和公有構(gòu)造器都各有用處温兼,我們需要理解他們各自的長處。靜態(tài)工廠通常更加合適武契,因此切忌第一反應(yīng)就是提供公有的構(gòu)造器募判,而不先考慮靜態(tài)工廠。
2.遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮用構(gòu)建器
這個(gè)就是Builder設(shè)計(jì)模式
3.用私有構(gòu)造器或者枚舉類型強(qiáng)化Singleton屬性
這個(gè)是單例模式的注意事項(xiàng)咒唆,選擇最好的單例模式實(shí)現(xiàn)
4.通過私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力
有時(shí)候需要編寫一些只包含靜態(tài)方法和靜態(tài)域的類届垫,這些類的名聲很不好,因?yàn)橛行┤嗽诿嫦驅(qū)ο蟮恼Z言中濫用這樣的類來編寫過程化的程序全释,盡管如此装处,他們也確實(shí)有它們的好處,比如常見的工具類java.lang.Math等浸船,都是這樣妄迁。方正這樣不可以實(shí)例化的類,最好把他的構(gòu)造器設(shè)置為私有李命。
例如:
public class UtilityClass{
private UtilityClass(){
throw new AssertionError();
}
}
5.避免創(chuàng)建不必要的對(duì)象
一般來說登淘,最好能重用對(duì)象而不是再每次需要的時(shí)候就創(chuàng)建一個(gè)相同功能的新對(duì)象,如果對(duì)象是不可變的项戴,他就始終可以被重用形帮。
簡單的例子:字符串
String s = new String("stringtest");//不要這樣做槽惫,因?yàn)樵撜Z句每次執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)新的String實(shí)例周叮,沒必要
// 改進(jìn)后的版本,這個(gè)版本只用了一個(gè)String實(shí)例辩撑,而不是每次執(zhí)行的時(shí)候都創(chuàng)建一個(gè)新的實(shí)例。對(duì)于再同一臺(tái)虛擬機(jī)中運(yùn)行的代碼仿耽,只要它們包含相同的字符串自字面常量合冀,該對(duì)象就可以被重用。
String s = "stringtest";
對(duì)于同時(shí)提供了靜態(tài)方法和構(gòu)造器方法的不可變類项贺,通尘桑可以使用靜態(tài)工廠方法而不是構(gòu)造器,以避免創(chuàng)建不必要的對(duì)象开缎。
優(yōu)先使用基本類型棕叫,而不是裝箱基本類型,當(dāng)心無意識(shí)的自動(dòng)裝箱奕删。
也不要錯(cuò)誤地認(rèn)為本條目暗示著“創(chuàng)建對(duì)象的代價(jià)非常昂貴俺泣,我們應(yīng)該盡可能地避免創(chuàng)建對(duì)象”,相反完残,由于小對(duì)象地構(gòu)造器制作很少量地顯示工作伏钠,所以,小對(duì)象地創(chuàng)建和回收動(dòng)作是非常廉價(jià)地谨设。
通過維護(hù)自己的對(duì)象池來避免創(chuàng)建對(duì)象比不是一種好的做法熟掂,除非池中的對(duì)象是非常重量級(jí)的,一般數(shù)據(jù)庫連接池常用扎拣。
6 消除過期的對(duì)象引用
不要以為Java有垃圾回收機(jī)制赴肚,能自動(dòng)管理內(nèi)存,自動(dòng)回收垃圾鹏秋,就可以不管了尊蚁,其實(shí)不然。
內(nèi)存泄漏的例子
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_CAPACITY = 16;
public Stack(){
elements = new Object[DEFAULT_CAPACITY];
}
public void push(Object object){
ensureCapacity();
elements[size++] = object;
}
public Object pup(){
if (size == 0){
throw new EmptyStackException();
}
return elements[size--];
}
private void ensureCapacity(){
if (elements.length == size){
elements = Arrays.copyOf(elements,size*2+1);
}
}
}
這段程序并沒有明顯的錯(cuò)誤侣夷,如果是棧先是增長横朋,然后再收縮,那么百拓,從棧中彈出來的對(duì)象將不會(huì)被當(dāng)作垃圾回收琴锭,因?yàn)槔锩娴臄?shù)組里引用著它,棧內(nèi)部維護(hù)著這些對(duì)象的過期引用衙传,過期引用就是指永遠(yuǎn)也不會(huì)再被解除的引用决帖。
這類問題的修復(fù)方法很簡單:一旦對(duì)象引用已經(jīng)過期,只需清空這些應(yīng)用即可蓖捶。
沒必要對(duì)于每一個(gè)對(duì)象引用地回,一旦程序不再用到它,就把它清空。清空對(duì)象引用應(yīng)該是一種例外刻像,而不是一種規(guī)范行為畅买,消除過期引用最好的辦法是讓包含該對(duì)象的變量結(jié)束其生命周期。一般而言细睡,只要類是自己管理內(nèi)存谷羞,程序員就應(yīng)該警惕內(nèi)存泄漏問題,一旦元素被釋放掉溜徙,則該元素中包含的任何對(duì)象引用都應(yīng)該被清空湃缎。
內(nèi)存泄漏的另一個(gè)常見來源是緩存,一旦你把對(duì)象放在緩存中蠢壹,他就很容易被遺忘掉嗓违,從而使得它不再有用之后很長一段時(shí)間內(nèi)仍然留在緩存中⊥济常可以用WeakHashMap
內(nèi)存泄漏的第三個(gè)常見來源是監(jiān)聽器和其他回掉靠瞎,一般都要取消注冊(cè),或者用弱引用
內(nèi)存泄漏通常不會(huì)表現(xiàn)成明顯的失敗求妹,所以他們可以再一個(gè)系統(tǒng)中存在很多年乏盐,往往通過仔細(xì)檢查代碼,借助于Heap刨析工具才能發(fā)現(xiàn)內(nèi)存泄漏問題制恍。
7 避免使用終結(jié)方法
終結(jié)方法(finalizer)通常是不可預(yù)測(cè)的父能,也是很危險(xiǎn)的,一般情況下是不必要的净神。
C++的析構(gòu)器是回收一個(gè)對(duì)象所占用資源的常規(guī)方法何吝,是構(gòu)造器所必須的對(duì)應(yīng)物,也可以用來回收其他的非內(nèi)存資源鹃唯,而在Java中爱榕,一般用try-finally塊來完成類似的工作