使用嵌套迭代器迭代两级结构

时间:2016-07-28 08:26:35

标签: java xml iterator stax

我有以下两级XML结构。一个方框列表,每个方框都包含一个抽屉列表。

<Boxes>
    <Box id="0">
        <Drawers>
            <Drawer id="0"/>
            <Drawer id="1"/>
            ...
        </Drawers>
    </Box>
    <Box id="1">
...
    </Box>
</Boxes>

我正在使用StAX对其进行解析,并通过两个Iterators公开结构:

  1. BoxIterator implements Iterator<Box>, Iterable<Box>
  2. Box implements Iterable<Drawer>
  3. DrawerIterator implements Iterator<Drawer>
  4. 然后我可以执行以下操作:

    BoxIterator boxList;
    for (Box box : boxList) {
      for (Drawer drawer : box) {
        drawer.getId()
      }
    }
    

    使用Iterators StAX XMLStreamReader,我们两人正在访问相同的基础BoxIterator.next()。如果我调用DrawerIterator.next(),它将影响后续调用Iterator时返回的结果,因为光标将移动到下一个框。

    这会违反StAX的合同吗? 有没有更好的方法使用equals迭代两级结构?

4 个答案:

答案 0 :(得分:5)

  

这会违反Iterator的合同吗?

没有

Java Iterator强加两个“合同”。第一个合同是Java接口本身,它声明了3种方法:hasNext()next()remove()。任何实现此Iterator接口的类都必须定义这些方法。

第二份合约定义了Iterator

的行为
  如果迭代包含更多元素,则

hasNext() [...]返回true。 [...] next()返回迭代中的下一个元素[和]如果迭代没有更多元素则抛出NoSuchElementException

这是整个合同。

如果基础XMLStreamReader已提前,则可能会导致BoxIterator和/或DrawerIterator陷入困境。或者,在错误的点调用BoxIterator.next()和/或DrawerIterator.next()可能会使迭代陷入混乱。但是,正确使用,例如在上面的示例代码中,它可以正常工作并大大简化了代码。您只需要记录迭代器的正确用法。

作为一个具体的例子,Scanner类实现了Iterator<String>,但还有许多其他方法可以推进基础流。如果Iterator类强加了合同,那么Scanner类本身就会违反它。

正如Ivan在评论中指出的那样,boxList不应该是class BoxIterator implements Iterator<Box>, Iterable<Box>类型。你真的应该:

class BoxList implements Iterable<Box> { ... }
class BoxIterator implements Iterator<Box> { ... }

BoxList boxList = ...;
for (Box box : boxList) {
  for (Drawer drawer : box) {
    drawer.getId()
  }
}

虽然从一个类实现IterableIterator对于您的用例在技术上并不是错误的,但它可能会导致混淆。

在另一个上下文中考虑此代码:

List<Box> boxList = Arrays.asList(box1, box2, box3, box4);
for(Box box : boxList) {
    // Do something
}
for(Box box : boxList) {
    // Do some more stuff
}

这里,boxList.iterator()被调用两次,以创建两个单独的Iterator<Box>实例,用于迭代两次框的列表。因为boxList可以多次迭代,所以每次迭代都需要一个新的迭代器实例。

在您的代码中:

BoxIterator boxList = new BoxIterator(xml_stream);
for (Box box : boxList) {
  for (Drawer drawer : box) {
    drawer.getId();
  }
}

因为您正在迭代流,所以不能(不倒回流或存储提取的对象)第二次迭代相同的节点。不需要第二类/对象;同一个对象既可以作为Iterable也可以作为Iterator ...它可以为你节省一个类/对象。

话虽如此,过早优化是万恶之源。一个类/对象的节省不值得可能混淆;您应该将BoxIterator分为BoxList implements Iterable<Box>BoxIterator implements Iterator<Box>

答案 1 :(得分:3)

由于hasNext()可以返回true,但next()可能会抛出NoSuchElementException,因此有可能违反合同。

hasNext()的合同是:

  

如果迭代有更多元素,则返回true。 (换句话说,如果next()返回一个元素而不是抛出异常,则返回true。)

但是在调用hasNext()next()之间可能会发生另一个迭代器可能已经移动了流位置,以便不再有元素。

但是,在您使用它的方式(嵌套循环)中,您不会遇到破损。

如果你要将迭代器传递给另一个进程,那么你可能会遇到这种破坏。

答案 2 :(得分:2)

您的代码唯一的设计问题是BoxIterator同时实现IteratorIterable。通常,Iterable对象每次调用Iterator方法时都会返回新的有状态iterator()。因此,两个迭代器之间不应该存在干扰,但是你需要一个状态对象才能正确地实现从内循环中退出(可能你已经有了这个,但为了清楚起见,我必须提一下)。

  1. 状态对象将像解析器的代理一样使用popEvent和peekEvent两种方法。在窥视迭代器将检查最后一个事件,但不会消耗它。在流行音乐上,他们会消耗最后一次活动。
  2. BoxIterable#iterator()将使用StartElement(Boxes)并在此之后返回迭代器。
  3. BoxIterator#hasNext()将查看事件并弹出它们,直到收到StartElement或EndElement。只有在收到StartElement(Box)时才会返回true。
  4. BoxIterator#next()将查看并弹出属性事件,直到收到StartElement或EndElement来初始化Box对象。
  5. Box#iterator()将使用StartElement(Drawers)事件,然后返回DrawerIterator。
  6. DrawerIterator#hasNext()将一直闪烁,直到收到StartElement或EndElement。只有当它是StartElement(Drawer)
  7. 时才会返回true
  8. DrawerIterator#next()将使用属性事件,直到收到EndElement(抽屉)。
  9. 您的用户代码几乎不会被修改:

    BoxIterable boxList;
    /*
     * boxList must be an BoxIterable, which on call to iterator() returns 
     * new BoxIterator initialized with current state of STaX parser
     */
    for (Box box : boxList) { 
      /* 
       * on following line new iterator is created and initialized 
       * with current state of parser 
       */
      for (Drawer drawer : box) { 
        drawer.getId()
      }
    }
    

答案 3 :(得分:0)

如果您正在仔细实施/覆盖next()&amp; hasNext()BoxIterator中的DrawerIterator种方法Iterator通过实施hasNext()接口。毋庸置疑,需要注意的显而易见的条件是true如果next()正在返回元素,则false应返回next() BoxIterator如果Iterable<Box>正在提供异常

但我无法理解的是你为什么要BoxIterator implements Iterator<Box>, Iterable<Box>实施iterator()

Iterable 由于Box的{​​{1}}接口覆盖BoxIterator方法总是会返回BoxIterator的实例。如果您背后没有任何其他目标,则无意在{{1}}中封装此功能。