' Collections.unmodifiableCollection'在构造函数中

时间:2016-01-28 20:52:04

标签: java constructor defensive-copy

在代码审查期间,我会看到像这样的构造函数:

Foo(Collection<String> words) {
    this.words = Collections.unmodifiableCollection(words);
}

这是保护班级内部状态的正确方法吗?如果没有,那么在构造函数中创建适当防御副本的惯用方法是什么?

3 个答案:

答案 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" );
    }
}

因此,为班级建立初始状态的正确方法是:

  1. 检查null
  2. 的输入参数
  3. 如果输入类型不保证是不可变的(与Collection一样),请制作防御性副本。在这种情况下,因为元素类型是不可变的(String),所以可以使用浅层副本,但如果它不是,则必须进行更深层次的复制。
  4. 对副本执行任何进一步的前提条件检查。 (在原始文件上执行此操作可能会让您受TOCTTOU次攻击。)

答案 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。