【问题标题】:Adding and removing items to a Guava ImmutableList向 Guava ImmutableList 添加和删除项目
【发布时间】:2012-10-17 15:34:30
【问题描述】:

在 Guava 中,是否有一种有效的方法可以向 ImmutableList 添加或删除项目(当然,在此过程中创建新列表)。

我能想到的最简单的方法是:

private ImmutableList<String> foos = ImmutableList.of();

public void addFoo(final String foo) {
    if (this.foos.isEmpty()) {
        foos = ImmutableList.of(foo);
    } else {
        foos = ImmutableList.<String>builder().addAll(foos).add(foo).build();
    }
}

public void removeFoo(final String foo) {
    final int index = this.foos.indexOf(foo);
    if (index > -1) {
        final Builder<String> builder = ImmutableList.<String>builder();
        if (index > 0) builder.addAll(this.foos.subList(0, index));
        final int size = this.foos.size();
        if (index < size - 1) builder.addAll(this.foos.subList(index+1, size));
        this.foos = builder.build();
    }
}

我想避免这样做:

public void removeFoo(final String foo) {
    final ArrayList<String> tmpList = Lists.newArrayList(this.foos);
    if(tmpList.remove(foo))this.foos=ImmutableList.copyOf(tmpList);
}

但不幸的是,它比我能想到的任何纯 Guava 方法都简单得多。我错过了什么吗?

【问题讨论】:

  • 你想解决什么更高层次的问题?如果您需要更改它们,也许您不应该处理不可变列表。
  • 我知道当使用可变列表作为数据持有者之间时,我可以轻松解决问题,但是 a) 我必须创建多余的中间集合 b) 我必须将 java.util 集合与 guava ImmutableCollections 混合,而我想坚持一种范式。而且我想使用 ImmutableList 因为我想在 getFoos() 方法中将其分发给客户端,而不会失去控制或不必创建大量 Collections.unmodifiableList 包装器对象。
  • 然后我将使用可变列表作为实例字段并在 getFoos() 中返回 ImmutableList.copyOf(list)。
  • @ChristophLeiter 是的,但这意味着 a) 每次读取都需要完全遍历(我正在考虑读取比添加/删除调用更常见的场景),b) 我d 必须同步所有内容以避免 ConcurrentModificationExceptions。当然,我上面的示例也没有同步,但它们不会抛出 ConcurrentModificationExceptions。

标签: java guava


【解决方案1】:

可以通过过滤来移除,不会创建中间的ArrayList或者builder,只会遍历列表一次:

public void removeFoo(final String foo) {
    foos = ImmutableList.copyOf(Collections2.filter(foos,
            Predicates.not(Predicates.equalTo(foo)));
}

对于添加,我没有看到更好的解决方案。

【讨论】:

  • 我当然想到了这一点(抱歉没有提及)。问题是:它删除了每一个出现,而 List.remove() 只删除了第一次出现,我正在寻找那个功能。仍然:+1
  • 那么我猜你当前的任何一个实现都和它一样好(你可以删除if (index &gt; 0)if (index &lt; size - 1),因为你会得到一个空的子列表)。根据列表的典型大小以及您是否实际删除某些内容,第二个可能会产生更少的垃圾,因为 ArrayList 的大小将正确,而构建器中的一个(或等效项)则不是。但它实际上是一个热点,所以这很重要吗?如果垃圾真的很重要,您还可以将两者结合起来并创建一个大小正确的 ArrayList 而不是构建器。
【解决方案2】:

ConcurrentModificationException 与并发和同步无关。同时访问可变的List 可能会损坏它和/或抛出异常(准备好所有3 种可能性)。您的代码不能以这种方式失败,但使用多线程它也不起作用:

  • 如果没有同步并且没有foosvolatile,则无法保证其他线程会看到您所做的更改。
  • 即使使用volatile,也可能会丢失一些更改,例如,当两个线程向foos 添加项目时,它们都可以从原始值开始,然后最后写入的那个获胜(并且只有它的项目被添加)。

您试图避免的代码是不可避免的。

  • “我必须创建多余的中间集合” - 是的,但没有免费的午餐:
    • 提前确定结果的大小,这意味着对整个列表进行额外的迭代
    • 或分配一个足够大的数组并在结果列表中复制所需的范围
    • 或分配足够大的数组并仅使用其中的一部分(节省时间和浪费内存)
    • 或创建不可变视图(既节省时间又节省内存,但以后可能会浪费时间)
  • AFAIK Frank 的答案实现了第一种可能性,如果谓词很快,这很好。
  • “我必须将 java.util Collections 与 guava ImmutableCollections 混合使用,而我想坚持一种范式。” - 是的,但是为了改变一个集合,需要一个可变集合。 ImmutableList.Builder 仅涵盖最常见的情况,允许以紧凑的方式处理它们。

您可能想看看persistent collections,它已针对此类操作进行了优化。但是,您不应该期望例如持久列表与ArrayListImmutableList 一样快。

【讨论】:

  • 好的,我没有在上面的例子中添加同步,因为它已经足够复杂了,但是添加同步会比使用 e.g. 时容易得多。数组列表。仍然:+1 用于持久收藏建议。也许番石榴不是寻找此类功能的正确位置。
  • @SeanPatrickFloyd:Guava 团队拒绝复制现有的东西,他们说持久化集合don't really fit in
  • @maaartinus 我有一个question,我在其中使用番石榴重试,并想与您核实我的代码是否是线程安全的,我正在做的事情是否正确?没有得到任何答案,所以想和你核实一下。
  • 还有一个ImmutableListWith implements ImmutableList 的替代方法,它由一个不可变列表、一个索引和一个值构成,并且是第一个不可变列表的薄包装器。还有一个单独的 ImmutableListWithout 包装器。但这比仅仅做明显的副本要复杂得多,而且分配可能更多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-18
  • 2014-07-09
  • 2018-09-19
  • 2014-12-23
  • 2011-07-09
  • 1970-01-01
相关资源
最近更新 更多