StringHolder是一个可变类(Mutable Class),不适合作为key
import java.util.HashSet;
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;
}
public static void main(String[] args) {
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());
}
编写不变类很容易。如果以下几点都为真,那么类就是不变的:
1. 所有的成员变量是 final
2. 类声明为 final
3. 在构造方法中,this的引用不会被修改
4. 如其包含的可变量的成员变量,如 array, collection。满足:
a. 是private的
b. 无返回值,或不会被外部调用
c. 是被引用对象的唯一引用
d. 在构造方法调用后,不会被修改状态
最后一组要求似乎挺复杂的,但其基本上意味着如果要存储对数组或其它可变对象的引用,就必须确保您的类对该可变对象拥有独占访问权(因为不然的话,其它类能够更改其状态),而且在构造后您不修改其状态。为允许不变对象存储对数组的引用,这种复杂性是必要的,因为 Java 语言没有办法强制不对 final 数组的元素进行修改。注:如果从传递给构造函数的参数中初始化数组引用或其它可变字段,您必须用防范措施将调用程序提供的参数或您无法确保具有独占访问权的其它信息复制到数组。否则,调用程序会在调用构造函数之后,修改数组的状态。清单 3 显示了编写一个存储调用程序提供的数组的不变对象的构造函数的正确方法(和错误方法)。
如:
public class ImmutableArrayHolder {
private final int[] theArray;
// Right way to write a constructor -- copy the array
public ImmutableArrayHolder(int[] anArray) {
this.theArray = (int[]) anArray.clone();
}
// 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();
}
有些数据项在程序生命期中一直保持常量,而有些会频繁更改。常量数据显然符合不变性,而状态复杂且频繁更改的对象通常不适合用不变类来实现。那么有时会更改,但更改又不太频繁的数据呢?有什么方法能让 有时更改的数据获得不变性的便利和线程安全的长处呢?
util.concurrent
包中的 CopyOnWriteArrayList
类是如何既利用不变性的能力,又仍允许偶尔修改的一个良好示例。它最适合于支持事件监听程序的类(如用户界面组件)使用。虽然事件监听程序的列表可以更改,但通常它更改的频繁性要比事件的生成少得多。
除了在修改列表时, CopyOnWriteArrayList
并不变更基本数组,而是创建新数组且废弃旧数组之外,它的行为与ArrayList
类非常相似。这意味着当调用程序获得迭代器(迭代器在内部保存对基本数组的引用)时,迭代器引用的数组实际上是不变的,从而可以无需同步或冒并发修改的风险进行遍历。这消除了在遍历前克隆列表或在遍历期间对列表进行同步的需要,这两个操作都很麻烦、易于出错,而且完全使性能恶化。如果遍历比插入或除去更加频繁(这在某些情况下是常有的事), CopyOnWriteArrayList
会提供更佳的性能和更方便的访问。