第十五條:使可變性最小
1.什么是不可變類刹帕?
(1)需要的所有參數(shù)必須在實(shí)例化的時(shí)候都傳進(jìn)去。
(2)對(duì)象中所有信息在對(duì)象的整個(gè)生命周期中都保持不變管挟。
2.使類不可變的原則
(1)不要提供任何修改對(duì)象狀態(tài)的方法。
(2)保證類不會(huì)被繼承弄捕。
(3)使所有的域都是final類型的。
(4)使所有的域都是私有類型的够滑。
(5)確保對(duì)于任何可變組件的互斥性蛤高。意思就是蚣旱,確保在該類的外部不會(huì)獲取到該類中可變對(duì)象的引用。比如下面這個(gè)例子:
public class MyObject{
private final List<String> list = new ArrayList<>;//可變對(duì)象
public List<String> getList() {
return new ArrayList(list);
}
public void setList(List<String> list) {
this.list = new ArrayList(list);
}
}
3.不可變類的優(yōu)點(diǎn)
(1)不可變類簡(jiǎn)單戴陡。
不可變類只有一種狀態(tài)塞绿,就是它被創(chuàng)建出來時(shí)候的狀態(tài),如果你要根據(jù)這個(gè)類進(jìn)行一系列復(fù)雜操作猜欺,那么這個(gè)操作無論在什么時(shí)候結(jié)果都是相同的位隶,所以你可以直接將結(jié)果緩存起來拷窜,在下一次執(zhí)行同樣操作的時(shí)候取出來开皿,而不必再進(jìn)行下一次操作。
(2)不可變類本質(zhì)上是線程安全的篮昧,它不需要同步鎖赋荆。
(3)對(duì)于不可變類,你永遠(yuǎn)都不需要實(shí)現(xiàn)拷貝方法懊昨。拷貝方法對(duì)它來說是沒有意義的窄潭。
(4)不可變類可以被自由的共享。
4.不可變對(duì)象的缺點(diǎn)
(1)對(duì)于每一個(gè)不同的值都需要?jiǎng)?chuàng)建一個(gè)新的對(duì)象酵颁。
5.缺點(diǎn)的彌補(bǔ)辦法
(1)先猜測(cè)一下經(jīng)常用到哪些多步驟的操作嫉你,然后將它們作為基本數(shù)據(jù)類型提供。比如Integer,它將值為-128到127的對(duì)象緩存起來躏惋,但調(diào)用valueOf(int i)的時(shí)候幽污,直接從緩存中拿,不用再重復(fù)創(chuàng)建提高效率簿姨。
(2)我們可以創(chuàng)建一個(gè)可變配套類距误。例如String是一個(gè)不可變類簸搞,它的可變配套類為StirngBuilder和StringBuffer。下面是一個(gè)例子准潭,我們現(xiàn)在實(shí)現(xiàn)一個(gè)復(fù)數(shù)類趁俊,對(duì)外提供一個(gè)相加的方法如下:
public class Complex {
private final double re;//實(shí)部
private final double im;//虛部
private Complex(double re, double im) {
this.re = re;
this.im = im;
}
public static Complex valueOf(double re, double im) {
return new Complex(re, im);
}
public double realPart() {
return re;
}
public double imaginaryPart() {
return im;
}
//復(fù)數(shù)相加
public Complex add(Complex c) {
return new Complex(re + c.re, im + c.im);
}
}
我們可以為他創(chuàng)建一個(gè)配套類:
public class ComplexBuilder {
private double re;
private double im;
private ComplexBuider(double re, double im) {
this.re = re;
this.im = im;
}
public static ComplexBuider newInstance(Complex c) {
return new ComplexBuilder(c.realPart(), c.imaginaryPart());
}
public void add(Complex c) {
this.re = this.re + c.realPart();
this.im = this.im + c.imaginaryPart();
}
public Complex toComplex() {
return Complex.valueOf(this.re, this.im);
}
}
在客戶端中我們?nèi)绻枰靡粋€(gè)復(fù)數(shù)和另一個(gè)復(fù)數(shù)相加100次,我們?nèi)绻挥肅omplexBuilder的話就像下面這樣刑然,算上最開始穿件的兩個(gè)實(shí)例寺擂,我們將會(huì)創(chuàng)建102個(gè)實(shí)例:
public class Test {
@Test
public void addNoBuiderTest() throws Exception{
Complex c1 = Complex.valueOf(1, 2);
Complex c2 = Complex.valueOf(2, 3);
for (int i = 0 ; i < 100 ; i++) {
c1 = c1.add(c2);
}
}
}
現(xiàn)在改用ComplexBuilder,現(xiàn)在我們只會(huì)創(chuàng)建4個(gè)實(shí)例:
public class Test {
@Test
public void addNoBuiderTest() throws Exception{
Complex c1 = Complex.valueOf(1, 2);
Complex c2 = Complex.valueOf(2, 3);
ComplexBuilder cb = ComplexBuider.newInstance(c1);
for (int i = 0 ; i < 100 ; i++) {
cb.add(c2);
}
c1 = cb.toComplex();
}
}
6.總結(jié)
(1)堅(jiān)決不要為每個(gè)getter都生成setter闰集。
(2)能將類做成不可變的就做成不可變的沽讹。
(3)一般比較小的值類都是需要做成不可變的。
(4)對(duì)于一些比較大的值類盡量考慮實(shí)現(xiàn)成不可變類武鲁。
(5)性能方面很有必要的時(shí)候才需提供配套類爽雄。
(6)如果真的不能作為不可變類,那就盡量限制其可變性
(7)對(duì)于一個(gè)類的初始化只能在構(gòu)造器或是靜態(tài)工廠中完成沐鼠。也就是說類的初始化操作(賦值之類的)只能執(zhí)行一次挚瘟,就是在構(gòu)造器或是靜態(tài)工廠中。這一點(diǎn)參考java類庫中的TimerTask類饲梭。