【问题标题】:Copy of collection收藏副本
【发布时间】:2016-02-26 15:11:11
【问题描述】:

您通常如何在 Java 中使用集合 getter?例如我有两个类。

public class A
{
    String s = "1";
    A () {}
}

public class B
{
     List<A> list;
     public B()
     {
       list = new ArrayList<>();
     }
     public List<A> getList()
     {
        return list;
     }

}

如果我有这样的吸气剂,我可以这样做:

B elem = new B();
elem.getList().add(new A());

所以它打破了 getter 的想法。

我可以的

 public List<A> getList()
 {
     return new ArrayList<>(list);
 }

但是,如果我修改结果的一些元素,原来的元素会被改变。

【问题讨论】:

  • 参考 SO Question Immutable vs Unmodifiable collection 可以给你一些想法
  • 如果要复制列表及其包含的所有内容,您必须使用深度复制功能,但这不是 Java 通常使用的方式。通过 getter 检索时能够编辑列表是正常的。
  • IMO,请注意这一点。虽然不变性在旨在关心的语言(如Clojure)中是一个很好的想法,但当您处理集合不变性时,它在 Java 中可能会变得非常昂贵。此外,它可能会导致运行时错误(读取List 的开发人员希望能够调用add,如果您通过Collections.unmodifiableList 返回不可变视图,这将导致Exception)。我宁愿把它留给最佳实践(您的客户应该知道他不应该在 getter 上致电 add 并让他在需要时进行复制)

标签: java collections


【解决方案1】:

Collection.unmodifiableList(list)返回一个不可修改的集合:

public List<A> getList() {
    return Collections.unmodifiableList(list);
}

引用 Javadoc:

返回指定列表的不可修改视图。此方法允许模块为用户提供对内部列表的“只读”访问权限。对返回列表的查询操作“通读”到指定列表,并尝试修改返回的列表,无论是直接还是通过其迭代器,都会导致UnsupportedOperationException

因此无法向返回的列表添加/删除元素,也无法修改其元素。

如果您想返回列表的深层克隆而不是不可修改的列表,请参阅this question

【讨论】:

  • 谢谢,但是有什么常见的方法可以返回副本吗?像通常的 C++ 行为一样。
【解决方案2】:

如果你需要一个深拷贝,你可以这样做

public List<A> getList() {
    return list.stream().map(A::copy).collect(toList());
}

更好的解决方案是首先不返回列表。你可以改为返回类似的东西。

public int getCount() { return list.size(); }

public String getASAt(int index) { return list.get(index).s; }

现在没有办法修改底层的A对象,也不需要冗余复制。

【讨论】:

  • 非常感谢。我不明白在 getter 中返回深层副本不是一个好主意,但有时你需要这样的 getter。
【解决方案3】:

通常我会在课堂上提供有意义的维护方法来管理列表。返回列表仅供查看:

public class Foo
{
    private List<Bar> bars = ....;
    // or even stricter, returning Iterator<Bar>
    /** be clear in javadoc the list is immutable */
    public List<Bar> getBars() {
       return Collections.unmodifiableList(bars);
    }

    public void addBar(Bar bar) {
        this.bars.add(bar);
        // and maybe some other works against bar
    }
    public void removeBar(Bar bar) {
        this.bars.remove(bar);
        // and maybe some other works against bar
    }
    // even something more meaningful
    public void removeExpiredBars() {
        // iterate thru this.bar and remove all bars with bar.isExpired()
    }
}

时刻提醒自己:getter(和 setter)的想法很糟糕。您不应该将您的课程设计为“从中获取数据”。你应该为你的班级提供有意义的行为。

【讨论】:

    【解决方案4】:

    您可以通过这种方式返回副本:

        public List<A> getList()
        {
           List<A> destination = new ArrayList<A> ( list.size());
           Collections.copy(destination , list);
           return destination ;
        }
    

    已编辑

    这是最短的版本:

        public List<A> getList()
        {
           return new ArrayList<A> ( list);
        }
    

    【讨论】:

    • 不是深拷贝。这只是列表的副本 - 修改源列表中的第 n 个元素会修改目标列表中的适当元素。
    • 是的,这是真的,请参阅#Peter Lawrey 回复
    • 而且,对于浅拷贝,它可以用更简单的方式完成:只需 return new ArrayList&lt;A&gt;(this.list);
    • 您编辑的版本仍然不是最短的版本。您可以简单地直接返回而不是使用变量然后返回
    猜你喜欢
    • 1970-01-01
    • 2015-06-20
    • 2011-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-30
    • 2013-12-01
    • 2022-01-20
    相关资源
    最近更新 更多