Effective Java 讀書筆記——39:必要時進行保護性拷貝
阿新 • • 發佈:2019-01-04
容易被破壞的內部約束條件
雖然如果沒有主動提供公共方法和變數,外部是無法修改類內部的資料的。但是,物件可能會在無意識的情況下提供幫助。例如,下面就是一個通過引用來修改類內部的資料,而破壞物件內部的約束條件的例子:
public final class Period { private final Date start; private final Date end; public Period(Date start, Date end) { if (start.compareTo(end) > 0) throw new IllegalArgumentException(start + " after " + end); this.start = start; this.end = end; } public Date start() { return start; } public Date end() { return end; }...
可以看到,在構造器中加入了約束條件,開始時間要小於結束時間,這似乎不可變。但是Date類本身是可變的,因此很容易在使上面的類違背約束條件。
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // Modifies internals of p!
System.out.println(p);
由於Date傳的是引用,通過這種方式,很容易修改Period類內部的Date的資料。
對構造器的每個可變引數進行保護性拷貝
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (this.start.compareTo(this.end) > 0)
throw new IllegalArgumentException(start + " after " + end);
}
注意,這裡的保護性拷貝是在檢查引數的有效性之前進行的
另一種方式
上一種方式 仍然不能完全避免修改Period例項的可能,請看下面的程式碼: start = new Date();
end = new Date();
p = new Period(start, end);
p.end().setYear(78); // Modifies internals of p!
System.out.println(p);
可以看到,通過獲取返回值的引用,仍然可以間接修改Period內的資料。
因此,只需要修改這兩個訪問方法,使其返回的是內部資料的保護性拷貝即可。
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
改成這樣一樣,就真正實現了Period不可變,無論使用什麼方法,都不能違背開始時間不落後於結束時間的約束。