0. 幾個問題
- 什么是不可變類?
- 不可變類的優缺點是什么?
- 常見的不可變類有哪些?String為什么要設計成不可變類?
- 如何自己設計一個不可變類?
帶著這幾個問題閱讀本文以期能對Java的不可變類有一個全面的了解。
1. 什么是不可變類
不可變類是指類的實例一旦創建后,不能改變其成員變量的值。
與之對應的,可變類的實例創建后可以改變其成員變量的值。
2. 不可變類的優缺點
- 優點:
- 效率,例如字符串常量池,字符串常量池可以將一些字符常量放在常量池中重復使用,避免每次都重新創建相同的對象、節省存儲空間。但如果字符串是可變的,此時相同內容的String還指向常量池的同一個內存空間,當某個變量改變了該內存的值時,其他遍歷的值也會發生改變。所以不符合常量池設計的初衷。
- 安全性,不可變對象天生是線程安全的,在不同線程共享對象,不需要同步機制,因為對象的值是固定的。
- 缺點:
- 資源開銷,對象需要頻繁的修改屬性,則每一次修改都會新創建一個對象,產生大量的資源開銷。
3. 常見的不可變類有哪些?String為什么要設計成不可變類?
常見的不可變類:String
Integer
Long
等類型
String設計成不可變類主要是出于效率和安全性考慮。
4. 如何自己設計一個不可變類?
- 類使用final修飾符修飾,保證類不能被繼承。
如果類可以被繼承會破壞類的不可變性機制,只要繼承類覆蓋父類的方法并且繼承類可以改變成員變量值,那么一旦子類以父類的形式出現時,不能保證當前類是否可變。 - 類的成員變量都應該是
private final
的,保證成員變量不可改變。 - 任何獲取/修改屬性的方法都不應作用于屬性本身。
- 不提供修改成員變量的方法,例如
setter
方法。 -
getter
方法不能返回對象本身,要返回對象的拷貝,防止對象外泄。 - 修改對象的屬性時要返回新對象
- 不提供修改成員變量的方法,例如
- 對成員變量的初始化通過構造器進行,并進行深拷貝。
如果使用傳入的參數直接賦值,則傳遞的只是引用,仍然可以通過外部變量改變它的值。
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
類的設計遵循以下原則:
- String類被final修飾,不可繼承
- string內部所有成員都設置為私有變量
- 不存在value的setter
- 并將value和offset設置為final。
- 當傳入可變數組value[]時,進行copy而不是直接將value[]復制給內部變量.
- 獲取value時不是直接返回對象引用,而是返回對象的copy.
這都符合上面總結的不變類型的特性,也保證了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();
}
輸出結果為:
s=hello world
s=hello_world