0. 幾個問題
- 什么是不可變類蝎亚?
- 不可變類的優(yōu)缺點是什么?
- 常見的不可變類有哪些先馆?String為什么要設(shè)計成不可變類发框?
- 如何自己設(shè)計一個不可變類?
帶著這幾個問題閱讀本文以期能對Java的不可變類有一個全面的了解煤墙。
1. 什么是不可變類
不可變類是指類的實例一旦創(chuàng)建后梅惯,不能改變其成員變量的值宪拥。
與之對應(yīng)的,可變類的實例創(chuàng)建后可以改變其成員變量的值铣减。
2. 不可變類的優(yōu)缺點
- 優(yōu)點:
- 效率她君,例如字符串常量池,字符串常量池可以將一些字符常量放在常量池中重復(fù)使用葫哗,避免每次都重新創(chuàng)建相同的對象缔刹、節(jié)省存儲空間。但如果字符串是可變的劣针,此時相同內(nèi)容的String還指向常量池的同一個內(nèi)存空間校镐,當(dāng)某個變量改變了該內(nèi)存的值時,其他遍歷的值也會發(fā)生改變捺典。所以不符合常量池設(shè)計的初衷鸟廓。
- 安全性,不可變對象天生是線程安全的襟己,在不同線程共享對象肝箱,不需要同步機(jī)制,因為對象的值是固定的稀蟋。
- 缺點:
- 資源開銷煌张,對象需要頻繁的修改屬性,則每一次修改都會新創(chuàng)建一個對象退客,產(chǎn)生大量的資源開銷骏融。
3. 常見的不可變類有哪些?String為什么要設(shè)計成不可變類萌狂?
常見的不可變類:String
Integer
Long
等類型
String設(shè)計成不可變類主要是出于效率和安全性考慮档玻。
4. 如何自己設(shè)計一個不可變類?
- 類使用final修飾符修飾茫藏,保證類不能被繼承误趴。
如果類可以被繼承會破壞類的不可變性機(jī)制,只要繼承類覆蓋父類的方法并且繼承類可以改變成員變量值务傲,那么一旦子類以父類的形式出現(xiàn)時凉当,不能保證當(dāng)前類是否可變。 - 類的成員變量都應(yīng)該是
private final
的售葡,保證成員變量不可改變看杭。 - 任何獲取/修改屬性的方法都不應(yīng)作用于屬性本身。
- 不提供修改成員變量的方法挟伙,例如
setter
方法楼雹。 -
getter
方法不能返回對象本身,要返回對象的拷貝,防止對象外泄贮缅。 - 修改對象的屬性時要返回新對象
- 不提供修改成員變量的方法挟伙,例如
- 對成員變量的初始化通過構(gòu)造器進(jìn)行榨咐,并進(jìn)行深拷貝。
如果使用傳入的參數(shù)直接賦值谴供,則傳遞的只是引用块茁,仍然可以通過外部變量改變它的值。
5. String類的不可變性分析
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
...
/**
* Allocates a new {@code String} so that it represents the sequence of
* characters currently contained in the character array argument. The
* contents of the character array are copied; subsequent modification of
* the character array does not affect the newly created string.
*
* @param value
* The initial value of the string
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
...
/**
* Converts this string to a new character array.
*
* @return a newly allocated character array whose length is the length
* of this string and whose contents are initialized to contain
* the character sequence represented by this string.
*/
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
}
可以看出String
類的設(shè)計遵循以下原則:
- String類被final修飾憔鬼,不可繼承
- string內(nèi)部所有成員都設(shè)置為私有變量
- 不存在value的setter
- 并將value和offset設(shè)置為final龟劲。
- 當(dāng)傳入可變數(shù)組value[]時胃夏,進(jìn)行copy而不是直接將value[]復(fù)制給內(nèi)部變量.
- 獲取value時不是直接返回對象引用轴或,而是返回對象的copy.
這都符合上面總結(jié)的不變類型的特性,也保證了String類型是不可變的類仰禀。
但是照雁,即使是不可變類,通過反射仍然可以改變其屬性的值:
String s = "hello world";
System.out.println("s=" + s);
try {
Field v = String.class.getDeclaredField("value");
v.setAccessible(true);
char[] c = (char[]) v.get(s);
c[5] = '_';
System.out.println("s=" + s);
} catch (NoSuchFieldException nfe) {
nfe.printStackTrace();
} catch (IllegalAccessException iae) {
iae.printStackTrace();
}
輸出結(jié)果為:
s=hello world
s=hello_world