【问题标题】:Designing an iterator in Java用Java设计一个迭代器
【发布时间】:2015-05-17 15:58:43
【问题描述】:

我遇到了许多需要迭代器的问题。通常,它们是简单的东西,您已经拥有可以遵循的基础数据结构。其他时候,它会变得更复杂。

一个例子是使用中序遍历遍历没有父链接的 BST。这要求您执行以下操作:

  • 在构造函数中创建堆栈。
  • 迭代到最左边的节点。
  • 存储有更多节点可以访问,以便从 hasNext() 轻松返回。
  • 存储下一个要访问的节点,以便从 next() 轻松返回。

您可以在 hasNext() 或 next() 中定位下一个节点。您还可以在构造函数或第一次调用 hasNext() 时定位第一个节点。


我的问题

对于在迭代器实现中的大部分工作,是否有标准或最佳实践?一种方式比另一种方式“更干净”吗?

【问题讨论】:

    标签: java iterator iterable


    【解决方案1】:

    首先,Iterator的合约要求hasNext如果元素多则返回truenext会在hasNext()==false抛出异常。

    这意味着有两种使用迭代器的方式:while (it.hasNext()) it.next()try { while (true) it.next(); } catch ...。后者不是一个好的做法,但必须支持。我提到这一点是因为你不能依赖在next 之前调用过hasNext。我发现这个要求通常是迭代器实现中不必要的复杂性的罪魁祸首。

    我的选择是使用具有 next 值的局部变量。如果next==null 下一个值未知(我们必须找到它),或者我们已经到达迭代的终点(hasNext() 将返回false 并且next() 将失败)。还要考虑下一个值未知时,我们可能处于迭代的末尾,但我们还没有意识到。

    Node next;
    
    public boolean hasNext() {
       //if the next value already known, do nothing
       if (next==null) {         
         //otherwise lookup the next value
         next=findNext();
       }
       //return true if the next value was found
       return next!=null;
    }
    
    public Node next() {
      if (next==null&&!hasNext()) {
         //here we have reached the end of the iteration
         throw new NoSuchElementException();
      } else {
         //either we alredy knowed the next element 
         //or it was found by hasNext
         Node result = next;
         next=null;
         return result;
      }   
    }
    
    private Node findNext() {
       //the actual iteration
    }
    

    关于中序遍历的情况,你应该保留一个堆栈(注意Stack的实现是基于数组和同步的,probebly最好使用Dequeue,比如LinkedList,从 Java 6 开始还支持 pushpop),以及每次调用 findNext 时知道如何恢复迭代的辅助状态。

    【讨论】:

    • 如果我们只使用 next() 方法(没有 hasNext()):在现代编程中返回 null 是不好的做法吗?是不是我们可以把这个模型称为坏的?
    • +1。此外,实施应避免findbugs.sourceforge.net/… 中描述的错误
    • @PiotrZych 如果您的意思是返回null 表示迭代结束,我认为这是一个糟糕的设计,因为预计不会以这种方式使用Iterator。接口不仅是一组方法签名,而且是每个方法的预期行为(所谓的“契约”)。
    • 谢谢; “我提到这个是因为你不能依赖 hasNext 在 next 之前被调用过。我发现这个要求通常是迭代器实现中不必要的复杂性的罪魁祸首。”是最有用的:)
    【解决方案2】:

    BST中序遍历可以通过简单的递归DFS(左孩子->节点->右孩子)来实现。

    回答您的问题:总的来说,我认为迭代器设计没有“最佳实践”,因为您的数据结构可以任意复杂。一些常见的规则:

    • 在任何情况下,您的迭代器都必须支持hasNext()next() 操作。如果你的数据结构是不可变的(或者删除不是典型的),remove() 方法应该抛出OperationNotSupportedException()
    • next() 方法应该在执行开始时检查 hasNext() 的值,如果 hasNext() 返回 false,则抛出 NoSuchElementException
    • 如果你的数据结构有迭代器,它应该实现Iterable<Type>接口并实现public Iterator<Type> iterator()方法。
    • 所以如果你实现Iterable接口,客户端代码可以使用foreach操作符来处理你的数据结构。如果这种行为是不可取的(例如,将foreach 与二分图结构一起使用可能会导致多个客户端错误,因为您究竟在迭代什么并不明显),您可能应该考虑另一种实现顺序迭代的方法。
    • 如果您的数据结构使用列表或集合,您的迭代器实现可以使用适当的集合迭代器。

    在任何情况下,您都应该谨慎设计迭代器。每个迭代器都与其数据结构紧密相连,应该一起设计。

    【讨论】:

      猜你喜欢
      • 2011-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-24
      • 1970-01-01
      • 1970-01-01
      • 2018-09-23
      • 1970-01-01
      相关资源
      最近更新 更多