昨天說了如何設計immutable類別,以及物件狀態改變時該怎麼辦,今天會提出另外兩個方法,並說明immutable類別有什麼缺點,有什麼解法。
除了像昨天介紹的方法一樣,透過constructor或方法取得新的物件,也可以預先宣告一些特定用途的物件,並且設為final
,不只可以讓物件變immutable,也可以讓其他使用者重複使用。
private final int x;
private final int y;
public static final Point ORIGIN = new Point(0, 0);
public static final Point ONE_TO_ONE = new Point(1, 1);
// Constructor
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "Point{" + "x=" + x + ", y=" + y + '}';
}
public static void main(String[] args) {
System.out.println("Original Point: " + Point.ORIGIN);
}
}
但如果有一些欄位在實體化之後沒有立即使用的需要,也許可以不用先設為final
,需要使用的時候在計算並賦予欄位值,可以讓效能更佳。舉例來說,並不是每次實體化後都會用到hash code,或許一開始把儲存hash code的欄位設為null
,後面需要在計算即可,就像Effective java的Item 71 lazy initialization介紹的方法一樣。
除了上面說的那兩個方法,還可以使用Item 1介紹過的static factories method,這個方法除了可以確保類別不能被繼承,被修改成mutable類別,而且還可以cache住物件,重複使用物件提升效能。
public final class Point {
private final int x;
private final int y;
// 防止透過constructor產生物件
private Point(int x, int y) {
this.x = x;
this.y = y;
}
// 靜態工廠方法,用於創建 Point
public static Point of(int x, int y) {
return new Point(x, y);
}
@Override public String toString() {
return "Point{" + "x=" + x + ", y=" + y + '}';
}
public static void main(String[] args) {
// 使用靜態工廠方法創建新的 Point
Point p1 = Point.of(0, 0);
System.out.println("Point: " + p1);
}
}
最後來說說immutable類別的缺點。由於每次有不同需求時,都要生成一個新的物件,這會影響系統的效能,舉例來說,BigInteger是一個immutable類別,如果對最低位數進行修改,需要生成一個新的物件,並且把其他位數都複製一次,這個過程會花費很多時間。 為了解決上述問題,可以參考下面的兩種解法: