在代码审查期间,我会看到像这样的构造函数:
Foo(Collection<String> words) {
this.words = Collections.unmodifiableCollection(words);
}
这是保护班级内部状态的正确方法吗?如果没有,那么在构造函数中创建适当防御副本的惯用方法是什么?
答案 0 :(得分:2)
它应该是,但它不正确,因为调用者仍然可以修改基础列表。
您应该制作防御性副本,而不是包装列表,例如使用Guava&#39; ImmutableList
。
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
),所以可以使用浅层副本,但如果它不是,则必须进行更深层次的复制。答案 1 :(得分:1)
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 ---------^^^^^^^^^^^^^^^^^^^^^^
答案 2 :(得分:0)
不,这并不能完全保护它。
我喜欢用来确保内容是不可变的成语:
public Breaker(Collection<String> words) {
this.words = Collections.unmodifiableCollection(
Arrays.asList(
words.toArray(
new String[words.size()]
)
)
);
}
这里的缺点是,如果传入HashSet或TreeSet,它将失去速度查找。如果你关心Hash或Tree特性,你可以做一些其他事情而不是将它转换为固定大小的List。