【问题标题】:Why does Iterator define the remove() operation?为什么 Iterator 定义 remove() 操作?
【发布时间】:2012-07-23 19:19:52
【问题描述】:

在 C# 中,IEnumerator 接口定义了一种遍历集合并查看元素的方法。我认为这非常有用,因为如果您将 IEnumerable<T> 传递给一个方法,它不会修改原始源代码。

但是,在 Java 中,Iterator 定义了 remove 操作以(可选!)允许删除元素。将Iterable<T> 传递给方法没有任何好处,因为该方法仍然可以修改原始集合。

remove可选性refused bequest 气味的一个示例,但忽略这一点(已经讨论过 here)我会对提示 @987654329 的设计决策感兴趣接口上要实现的@事件。

导致remove 被添加到Iterator 的设计决策是什么?

换句话说,在IEnumerator 上明确没有定义remove 的C# 设计决策是什么?

【问题讨论】:

    标签: c# java iterator ienumerator


    【解决方案1】:

    Iterator 能够在迭代期间删除元素。您不能使用迭代器迭代集合并使用该集合的 remove() 方法从目标集合中删除元素。您将在下一次调用Iterator.next() 时获得ConcurrentModificationException,因为迭代器无法知道集合究竟是如何更改的,也无法知道如何继续迭代。

    当您使用 remove() 的迭代器时,它知道集合是如何更改的。此外,实际上您不能删除集合的任何元素,只能删除当前元素。这简化了迭代的继续。

    关于传递迭代器或可迭代的优点:您始终可以使用Collection.unmodifireableSet()Collection.unmodifireableList() 来防止修改您的集合。

    【讨论】:

    • unmodifiableXYZ 只是运行时的东西,我喜欢静态不变量。我对导致添加 remove 的设计决策以及 C# 不选择的原因更感兴趣。
    • 使用unmodifiableXYZ更糟糕的是如果有人传递了一个已经被包裹的XYZ,你可能会包裹两次,除非你使用反射来工作,否则你无法避免它出来。虽然我相信实施会对此进行优化,但对我来说仍然看起来太糟糕了。
    • 看了一下OpenJDK的实现,似乎没有避免任何多重包装。这意味着在一个复杂的应用程序中,您最终可能会将一个简单的ArrayList<T> 包装 100 次或更多次。
    【解决方案2】:

    在某些情况下,您希望能够使用迭代器删除元素,因为这是最有效的方法。例如,当遍历链接的数据结构(例如链表)时,使用迭代器删除是一个 O(1) 操作......与通过 List.remove() 操作的 O(N) 相比。

    当然,许多集合的设计是为了在集合期间通过Iterator.remove()以外的任何其他方式修改集合将导致ConcurrentModificationException


    如果您不想通过集合迭代器进行修改,则使用 Collection.unmodifiableXxxx 包装它并使用它的迭代器将产生预期的效果。或者,我认为 Apache Commons 提供了一个简单的不可修改的迭代器包装器。


    顺便说一句,IEnumerableIterator 有相同的“气味”。看看reset() 方法。我也很好奇 C# LinkedList 类如何处理 O(N) 删除问题。它似乎是通过暴露列表的内部 ...以FirstLast 属性的形式实现的,其值为LinkedListNode 引用。这违反了另一个设计原则......并且(IMO)比Iterator.remove()危险得多。

    【讨论】:

    • 我意识到reset 是相同的气味,但是(正如我试图在问题中陈述的那样),我对此并不真正感兴趣。 reset 不允许任何人删除容器的内容。我的主要争论点是,无论方法如何处理 C# 的可迭代对象(不考虑反射),它都会以相同的方式离开集合。
    • @JeffFoster - 正如我所说,Java 迭代器不提供这种保证......但如果你想要它,有一些简单而廉价的方法来实现它。
    • 是的,我只是想了解他们为什么不提供该保证。为了支持(或不支持)remove,Java / C# 在库设计级别做出了哪些妥协。
    • 我认为我的回答涵盖了设计妥协。这是为了在集合遍历期间实现有效的删除,而不会将集合数据结构的内部暴露给客户端。最重要的是,很明显,Java 设计者(显然)不认为提供不可修改的迭代器很重要。我有点同意这一点。
    • 我认为不会(至少不是硬币的另一面)。如果它允许有效删除,为什么 C# 不需要添加它?
    【解决方案3】:

    这可能是由于在迭代集合时从集合中删除项目一直是导致错误和奇怪行为的原因。从阅读文档中可以看出,Java 在运行时强制 remove() 每次调用 next() 时只调用一次,这让我认为它刚刚被添加以防止人们在迭代列表时弄乱从列表中删除数据。

    【讨论】:

      【解决方案4】:

      这实际上是 Java 的一个很棒的特性。您可能很清楚,在 .NET 中遍历列表以删除元素(其中有许多用例)时,您只有两个选项。

      var listToRemove = new List<T>(originalList);
      foreach (var item in originalList)
      {
          ...
          if (...)
          {
              listToRemove.Add(item)
          }
          ...
      }
      
      foreach (var item in listToRemove)
      {
          originalList.Remove(item);
      }
      

      var iterationList = new List<T>(originalList);
      for (int i = 0; i < iterationList.Count; i++)
      {
          ...
          if (...)
          {
              originalList.RemoveAt(i);
          }
          ...
      }
      

      现在,我更喜欢第二个,但在 Java 中我不需要所有这些,因为当我在一个项目上时,我可以删除它,但迭代仍将继续!老实说,虽然它可能看起来不合适,但它在很多方面确实是一种优化。

      【讨论】:

      • 相反,我认为这很可恶。它阻止了我将一组元素传递给一个方法,而不知道该方法是否会烤我的容器:) 我对导致它被添加的设计决策感兴趣,特别是为什么它不在 C# 中。
      • 关注@StephenC 的帖子,以防止它被toasting your container - 但我认为如果你想找出Java 设计人员为什么要实现它以及为什么Microsoft 会在错误的地方问这个问题没有。明白我的意思了吗?但请考虑上述情况,并考虑这样做的效率有多高。另外,这是您控制的方法还是来自您正在使用的框架的方法?
      • 你的例子是完全错误的。 C# 和 Java 都为List&lt;T&gt; 提供了RemoveAt 方法。因此,您的 Java 代码风格也适用于 C#。如果您的 C# 代码使用 foreach 结构,您也应该在 Java 中使用 for(:)。你会发现你实际上不能在这样的循环中调用remove 方法(因为你没有明确引用Iterator),所以你的Java“好处”消失了。
      猜你喜欢
      • 1970-01-01
      • 2018-03-25
      • 2010-12-13
      • 1970-01-01
      • 1970-01-01
      • 2014-12-07
      • 2020-09-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多