本章的主題是創(chuàng)建和銷毀對象:何時以及如何創(chuàng)建對象婉烟,何時以及如何避免創(chuàng)建對象,如何確保它們能夠適時地銷毀,以及如何管理對象銷毀之前必須進行的各種清理動作育瓜。
[toc]
一般來所,最好能重用對象而不是每次需要的時候就創(chuàng)建一個相同功能的新對象栽烂。重用方式既快速躏仇,又流行。如果對象是不可變的(immutable)(見第 15 條)愕鼓,它就始終可以被重用钙态。
String
書中舉了一個極端的反面例子:
String s = new String("stringette"); // DON'T DO THIS!
該語句每次執(zhí)行的時候都創(chuàng)建一個新的String
實例,但是這是多余的,("stringette")
本身就是一個 String 實例锰霜,如果此語句在循環(huán)中西雀,或是頻繁調(diào)用的方法中,將會創(chuàng)建出成千上萬不必要的 String 實例驻子。
改進后的版本如下所示:
String s = "stringette";
這個版本只用了一個 String 實例,而不是每次執(zhí)行的時候都創(chuàng)建一個新的實例估灿,而且它可以保證崇呵,對于所有在同一臺虛擬機中運行的代碼,只要它們包含相同的字符串字面常量馅袁,該對象就會被重用域慷。【這點跟 JVM 有關(guān),上一個版本每次都會 new 一個實例犹褒,此版本將先查找字符串池抵窒,在判斷是否創(chuàng)建對象】
原理:
- 在JAVA虛擬機(JVM)中存在著一個字符串池,其中保存著很多String對象叠骑,并且可以被共享使用李皇,因此它提高了效率。由于String類是final的宙枷,它的值一經(jīng)創(chuàng)建就不可改變掉房,因此我們不用擔心String對象共享而帶來程序的混亂。字符串池由String類維護慰丛,我們可以調(diào)用intern()方法來訪問字符串池卓囚。
- 而改進版本的這行代碼被執(zhí)行的時候,JAVA虛擬機首先在字符串池中查找是否已經(jīng)存在了值為"abc"的這么一個對象璧帝,它的判斷依據(jù)是String類equals(Object obj)方法的返回值捍岳。如果有,則不再創(chuàng)建新的對象睬隶,直接返回已存在對象的引用锣夹;如果沒有,則先創(chuàng)建這個對象苏潜,然后把它加入到字符串池中银萍,再將它的引用返回。
靜態(tài)工廠
對于同時提供了靜態(tài)工廠方法(見第1條)和構(gòu)造器的不可變類恤左,通程剑可以使用靜態(tài)工廠方法而不是構(gòu)造器。例如飞袋,Boolean.valueOf(String)
和 Boolean(String)
戳气,因為構(gòu)造器每次被調(diào)用的時候都會創(chuàng)建一個新的對象,而靜態(tài)工廠方法則從來不要求巧鸭,也不會這樣做瓶您。【構(gòu)造器需要 new 操作纲仍,靜態(tài)方法在 JVM 中早已分配固定的空間】
已知不會被修改的對象
除了重用不可變的對象之外呀袱,也可以重用那些已知不會被修改的可變對象。下面是一個比較微妙郑叠、也比較常見的反面例子夜赵。
其中設(shè)計可變的 Date對象,它們的值一旦計算出來之后就不再變化乡革。這個類建立了一個模型:其中有一個人寇僧,并有一個isBabyBoomer方法摊腋,用來檢驗這個人是否為一個"baby boomer(生育高峰期出身的小孩)",換句話所就是檢驗這個人是否出生于1946年至1964年期間婉宰。
public class Person {
private final Date birthDate;
// Other fields, methods, and constructor omitted
// DON'T DO THIS!
public boolean isBabyBoomer() {
// Unnecessary allocation of expensive object
Calendar gmtCal =
Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
Date boomStart = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
Date boomEnd = gmtCal.getTime();
return birthDate.compareTo(boomStart) >= 0 &&
birthDate.compareTo(boomEnd) < 0;
}
}
isBabyBoomer
每次被調(diào)用的時候歌豺,都會新建一個Calendar、一個TimeZone和兩個Date實例心包,這是不必要的開銷。
靜態(tài)的初始化器
此版本用一個靜態(tài)的初始化器(initializer)馒铃,避免了這種效率低下的情況蟹腾。
class Person {
private final Date birthDate;
// Other fields, methods, and constructor omitted
/**
* The starting and ending dates of the baby boom.
*/
private static final Date BOOM_START;
private static final Date BOOM_END;
static {
Calendar gmtCal =
Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_START = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_END = gmtCal.getTime();
}
public boolean isBabyBoomer() {
return birthDate.compareTo(BOOM_START) >= 0 &&
birthDate.compareTo(BOOM_END) < 0;
}
}
- 改進后的
Person
類只在初始化的時候創(chuàng)建 Calendar、TimeZone 和 Date 實例一次区宇,而不是在每次調(diào)用 isBabyBoomer 的時候創(chuàng)建這些實例娃殖。 - 在作者的機器上,沒調(diào)用一千萬此议谷,原版本需要 32 000ms 炉爆,而改進版本只需要 130 ms ,大約快了250倍卧晓。性能顯著提高芬首,代碼含義也更加清晰。
- 可以通過延遲初始化(lazily initializing(見第71條))逼裆,將
BOOM_START
BOOM_END
初始化延遲到第一次調(diào)用isBabyBoomer
的時候郁稍,但不建議,這樣會是方法的實現(xiàn)更加浮渣胜宇,無法將性能顯著提高到超過已經(jīng)達到的水平(見第55條)耀怜。
自動裝箱(autoboxing)
在Java1.5發(fā)行版中,允許程序員將基本類型和他們的裝箱基本類型(包裝類(Boxed Primitive Type))混用桐愉,感覺很神奇财破,它使得基本類型和它們的包裝類之間變得模糊,其實只是語法糖从诲,底層依然老老實實的構(gòu)造裝箱基本類型實例【可通過反編譯查看】左痢。如果無意識的在循環(huán)中使用了此技術(shù),將創(chuàng)建非常多個中間對象盏求,使性能繼續(xù)下降【Long sum
抖锥,long i
】:
// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
要優(yōu)先使用基本類型而不是裝箱基本類型,要當心無意識的自動裝箱碎罚。
總結(jié)
不要錯誤地認為本條目所介紹的內(nèi)容暗示著“創(chuàng)建對象的代價非常昂貴磅废,我們應(yīng)該要盡可能地避免創(chuàng)建對象”。相反荆烈,由于小對象的構(gòu)造器只做很少量的顯式工作拯勉,所以小對象的創(chuàng)建和回收動作是非常廉價的竟趾,特別是現(xiàn)代的JVM實現(xiàn)上更是如此。通過創(chuàng)建附加的對象宫峦,提升程序的清晰性岔帽、簡潔性和功能性,這通常是好事导绷。
- 除非是非常重量級的對象犀勒,如數(shù)據(jù)庫鏈接池等,否則應(yīng)該避免維護自己的對象池(object pool)來避免創(chuàng)建對象⊥浊現(xiàn)代的JVM實現(xiàn)具有高度優(yōu)化的垃圾回收器(CG)贾费,其性能很容易就會超過輕量級對象池的性能。
本條目對象的是第39條中有關(guān)“保護性拷貝(defensive copying)”的內(nèi)容檐盟。本條目提及“當你應(yīng)該重用現(xiàn)有對象的時候褂萧,請不要創(chuàng)建新的對象”,而第39條則說“當你應(yīng)該創(chuàng)建新對象的時候葵萎,請不要重用現(xiàn)有對象”导犹。注意,在提倡使用保護性拷貝的時候羡忘,因重用對象而付出的代價要遠遠大于因創(chuàng)建重復(fù)對象而付出的代價谎痢。必要時如果沒能實施保護性拷貝,將會導(dǎo)致潛在的錯誤和安全漏洞壳坪;而不必要地創(chuàng)建對象則只會影響程序的風(fēng)格和性能舶得。
- 類似于一個副本的概念:假設(shè)在圖書館借了一本書,如果你直接在上面做筆記爽蝴,但這本書是共享的沐批,將影響他人借閱。如果你是花錢復(fù)印一本蝎亚,在復(fù)印書上做筆記就不同了九孩。