不变类的好处
原帖:http://www.ibm.com/developerworks/cn/java/j-jtp02183/
不变对象是指在实例化后其外部可见状态无法更改的对象。Java 类库中的 String 、 Integer 和 BigDecimal 类就是不变对象的示例 ― 它们表示在对象的生命期内无法更改的单个值。
不变性的长处
如果正确使用不变类,它们会极大地简化编程。因为它们只能处于一种状态,所以只要正确构造了它们,就决不会陷入不一致的状态。您不必复制或克隆不变对象,就能自由地共享和高速缓存对它们的引用;您可以高速缓存它们的字段或其方法的结果,而不用担心值会不会变成失效的或与对象的其它状态不一致。不变类通常产生最好的映射键。而且,它们本来就是线程安全的,所以不必在线程间同步对它们的访问。
自由高速缓存
因为不变对象的值没有更改的危险,所以可以自由地高速缓存对它们的引用,而且可以肯定以后的引用仍将引用同一个值。同样地,因为它们的特性无法更改,所以您可以高速缓存它们的字段和其方法的结果。
如果对象是可变的,就必须在存储对其的引用时引起注意。请考虑清单 1 中的代码,其中排列了两个由调度程序执行的任务。目的是:现在启动第一个任务,而在某一天启动第二个任务。
清单 1. 可变的 Date 对象的潜在问题
Date d = new Date(); Scheduler.scheduleTask(task1, d); d.setTime(d.getTime() + ONE_DAY); scheduler.scheduleTask(task2, d);
public class StringHolder { private String string; public StringHolder(String s) { this.string = s; } public String getString() { return string; } public void setString(String string) { this.string = string; } public boolean equals(Object o) { if (this == o) return true; else if (o == null || !(o instanceof StringHolder)) return false; else { final StringHolder other = (StringHolder) o; if (string == null) return (other.string == null); else return string.equals(other.string); } } public int hashCode() { return (string != null ? string.hashCode() : 0); } public String toString() { return string; } ... StringHolder sh = new StringHolder("blert"); HashSet h = new HashSet(); h.add(sh); sh.setString("moo"); System.out.println(h.contains(sh)); System.out.println(h.size()); System.out.println(h.iterator().next()); }Flyweight 模式不变性启用了 Flyweight 模式,该模式利用共享使得用对象有效地表示大量细颗粒度的对象变得容易。例如,您可能希望用一个对象来表示字处理文档中的每个字符或图像中的每个像素,但这一策略的幼稚实现将会对内存使用和内存管理开销产生高得惊人的花费。Flyweight 模式采用工厂方法来分配对不变的细颗粒度对象的引用,并通过仅使一个对象实例与字母“a”对应来利用共享缩减对象数。有关 Flyweight 模式的更多信息,请参阅经典书籍 Design Patterns(Gamma 等著;请参阅 参考资料)。
class ImmutableArrayHolder { private final int[] theArray; // Right way to write a constructor -- copy the array public ImmutableArrayHolder(int[] anArray) { this.theArray = (int[]) anArray.clone(); } // Wrong way to write a constructor -- copy the reference // The caller could change the array after the call to the constructor public ImmutableArrayHolder(int[] anArray) { this.theArray = anArray; } // Right way to write an accessor -- don't expose the array reference public int getArrayLength() { return theArray.length } public int getArray(int n) { return theArray[n]; } // Right way to write an accessor -- use clone() public int[] getArray() { return (int[]) theArray.clone(); } // Wrong way to write an accessor -- expose the array reference // A caller could get the array reference and then change the contents public int[] getArray() { return theArray }}