不可變物件
如果一個物件的狀態在構造後不能改變,則該物件被認為是不可變的,對不可變物件的最大依賴被廣泛認為是一種建立簡單、可靠程式碼的合理策略。
不可變物件在併發應用程式中特別有用,由於它們不能改變狀態,因此它們不會被執行緒干擾破壞或在不一致的狀態下觀察。
程式設計師通常不願意使用不可變物件,因為他們擔心建立新物件的成本而不是就地更新物件的成本,物件建立的影響經常被高估,並且可以通過與不可變物件相關聯的一些效率來抵消,這些包括由於垃圾收集而減少的開銷,以及消除保護可變物件免於損壞所需的程式碼。
以下小節採用其例項可變的類,並從中派生出具有不可變例項的類,通過這樣做,它們為這種轉換提供了一般規則,並演示了不可變物件的一些優點。
同步類示例
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(); //Statement 1
String myColorName = color.getName(); //Statement 2
如果另一個執行緒在語句1之後但在語句2之前呼叫color.set
,則myColorInt
的值將與myColorName
的值不匹配,為了避免這種結果,必須將兩個語句繫結在一起:
synchronized (color) {
int myColorInt = color.getRGB();
String myColorName = color.getName();
}
這種不一致只適用於可變物件 — 對於不可變版本的SynchronizedRGB
,它不會是一個問題。
一種定義不可變物件的策略
以下規則定義了用於建立不可變物件的簡單策略,並非所有記錄為“不可變”的類都遵循這些規則。這並不一定意味著這些類的創造者是草率的 — 他們可能有充分的理由相信他們類的例項在構造後永遠不會改變,但是,這種策略需要複雜的分析,不適合初學者。
- 不要提供“
setter
”方法 — 修改欄位或欄位引用的物件的方法。 - 使所有欄位為
final
和private
。 - 不允許子類重寫方法,最簡單的方法是將類宣告為
final
,更復雜的方法是使建構函式為private
並在工廠方法中構造例項。 -
如果例項欄位包含對可變物件的引用,則不允許更改這些物件:
- 不要提供修改可變物件的方法。
- 不要共享對可變物件的引用,永遠不要儲存對傳遞給建構函式的外部可變物件的引用,如有必要,建立副本並儲存對副本的引用,同樣,必要時建立內部可變物件的副本,以避免在方法中返回原始物件。
將此策略應用於SynchronizedRGB
會導致以下步驟:
- 這個類中有兩個
setter
方法,第一個方法set
,任意改變物件,在類的不可變版本中不存在,第二個方法invert
,可以通過讓它建立一個新物件而不是修改現有物件來進行調整。 - 所有欄位都已為
private
,他們進一步獲得final
。 - 該類本身被宣告為
final
。 - 只有一個欄位引用一個物件,該物件本身是不可變的,因此,不需要防止改變“包含的”可變物件的狀態的保護措施。
在這些更改之後,我們有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);
}
}