6.不可變對象(Immutable Object)
如果一個對象在創建之後就不可以改變它的狀態,則這個對象被認爲是不可變的(immutable)。最大化依賴不可變對象來作爲創建簡單,可靠代碼的一種正確的策略,被廣泛接受。
不可變對象在併發程序中尤其有用,因爲它們不可以改變狀態,所以它們不會在線程交織中崩潰或是被看到不一致的狀態。
程序員進程不願意使用不可變對象,他們擔心創建新對象比更新一個對象要付出更多代價。創建新對象的影響往往被過高估計,這個代價有時可以被不可變對象的效率所抵消。這些包括減少垃圾回收的額外開銷和爲了保護可變對象崩潰而增加的額外代碼的開銷。
6.1 一個同步對象的例子
類SynchronizedRGB,定義了一個表示顏色的對象。它的一個對象表示一個顏色,其有三個整數表示的三原色的值決定,並且用一個字符串表示顏色名。
public class SynchronizedRGB { // Values must be between 0 and 255. private int red; private int green; private int blue; private String name; private void check(int red, int green, int blue) { if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { throw new IllegalArgumentException(); } } public SynchronizedRGB(int red, int green, int blue, String name) { check(red, green, blue); this.red = red; this.green = green; this.blue = blue; this.name = name; } public void set(int red, int green, int blue, String name) { check(red, green, blue); synchronized (this) { this.red = red; this.green = green; this.blue = blue; this.name = name; } } public synchronized int getRGB() { return ((red << 16) | (green << 8) | blue); } public synchronized String getName() { return name; } public synchronized void invert() { red = 255 - red; green = 255 - green; blue = 255 - blue; name = "Inverse of " + name; } }
|
SynchronizedRGB必須小心使用,避免出現不一致的狀態,例如,一個執行下面代碼的線程:
SynchronizedRGB color = new SynchronizedRGB(0, 0, 0, "Pitch Black"); ... int myColorInt = color.getRGB(); //語句 1 String myColorName = color.getName(); //語句 2 |
如果另外一個線程在語句1和語句2之間調用color.set。則myColorInt的值就和myColorName的值不匹配。爲了避免這個結果,這兩個語句必須要幫定在一起:
synchronized (color) { int myColorInt = color.getRGB(); String myColorName = color.getName(); } |
這個不一致只在可變對象上產生——在SynchronizedRGB的不可變版本上是不會出現問題的。
6.2 定義不可變對象的策略
下面的規則定義了創建不可變對象的簡單規則。不是所有的不可變類都要遵循這裏的規則。這並不意味着這些類的創建者可以草率的——他們可能有很好的理由相信他們的類的實例創建後不會改變狀態。然而,這樣的策略需要富有經驗的分析,不時新手能做到的。
1. 不要提供“setter”方法——修改字段的方法或字段引用的對象
2. 讓所有的字段時“final”和“private”的
3. 不要讓子類繼承方法。最簡單的方法就是聲明類爲final。更精巧的做法是讓構造器是“private”的,並用工廠方法創建實例。
4. 如果實例字段中包含指向可變對象的引用,不要允許那些對象被改變:
a) 不要提供改變可變對象的方法
b) 不要共享指向引用對象的方法。從不存儲傳遞到構造器的外部的可變對象的引用;如果有必要,創建一個拷貝,存儲拷貝的引用。相似的,創建內部可變對象的拷貝來避免在方法中直接返回原版對象。
將這些規則應用到SynchronizedRGB,導致了以下的步驟:
1. 這個類裏有兩個“setter”方法。第一個,set,可以任意改變對象,在這個類的不變版本中沒有存在的必要了。第二個,invert,可以修改成創建新的對象,而不是修改原來的對象。
2. 所有的字段都已經私有了;他們被進一步的修飾成final。
3. 類本身被聲明成了final。
4. 只有一個指向對象的引用,這個對象本身是不可變的。所有,沒有必要對包含的可變對象採取措施。
經過這些步驟,我們得到了ImmutableRGB,如下:
final public class ImmutableRGB { // Values must be between 0 and 255. final private int red; final private int green; final private int blue; final private String name; private void check(int red, int green, int blue) { if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { throw new IllegalArgumentException(); } } public ImmutableRGB(int red, int green, int blue, String name) { check(red, green, blue); this.red = red; this.green = green; this.blue = blue; this.name = name; } public int getRGB() { return ((red << 16) | (green << 8) | blue); } public String getName() { return name; } public ImmutableRGB invert() { return new ImmutableRGB(255 - red, 255 - green, 255 - blue, "Inverse of " + name); } }
|