在代码审查期间,我不时看到这样的构造函数:
Foo(Collection<String> words) {
this.words = Collections.unmodifiableCollection(words);
}
这是保护班级内部状态的正确方法吗?如果不是,在构造函数中创建适当的防御性副本的惯用方法是什么?
在代码审查期间,我不时看到这样的构造函数:
Foo(Collection<String> words) {
this.words = Collections.unmodifiableCollection(words);
}
这是保护班级内部状态的正确方法吗?如果不是,在构造函数中创建适当的防御性副本的惯用方法是什么?
应该是,但它不正确,因为调用者仍然可以修改基础列表。
您应该制作一个防御性副本,而不是包装列表,例如使用 GuavaImmutableList
代替。
Foo(Collection<String> words) {
if (words == null) {
throw new NullPointerException( "words cannot be null" );
}
this.words = ImmutableList.copyOf(words);
if (this.words.isEmpty()) { //example extra precondition
throw new IllegalArgumentException( "words can't be empty" );
}
}
所以为类建立初始状态的正确方法是:
null
。Collection
),请制作防御性副本。在这种情况下,因为元素类型是不可变的String
(Collections.unmodifiableCollection(words);
仅创建您无法修改的包装器words
,但这并不意味着words
不能在其他地方进行修改。例如:
List<String> words = new ArrayList<>();
words.add("foo");
Collection<String> fixed = Collections.unmodifiableCollection(words);
System.out.println(fixed);
words.add("bar");
System.out.println(fixed);
结果:
[foo]
[foo, bar]
如果要保留words
不可修改集合中的当前状态,则需要从传递的集合中创建自己的元素副本,然后将其包装为Collections.unmodifiableCollection(wordsCopy);
就像您只想保留单词的顺序一样:
this.words = Collections.unmodifiableCollection(new ArrayList<>(words));
// separate list holding current words ---------^^^^^^^^^^^^^^^^^^^^^^
不,这并不能完全保护它。
我喜欢用来确保内容不可变的成语:
public Breaker(Collection<String> words) {
this.words = Collections.unmodifiableCollection(
Arrays.asList(
words.toArray(
new String[words.size()]
)
)
);
}
但是,这里的缺点是,如果您传入 HashSet 或 TreeSet,它将失去速度查找。如果您关心 Hash 或 Tree 特性,您可以做一些事情,而不是将其转换为固定大小的 List。