Java 使用嵌套迭代器在两层结构上迭代

Java 使用嵌套迭代器在两层结构上迭代,java,xml,iterator,stax,Java,Xml,Iterator,Stax,我有以下两层XML结构。盒子列表,每个盒子包含一个抽屉列表 <Boxes> <Box id="0"> <Drawers> <Drawer id="0"/> <Drawer id="1"/> ... </Drawers> </Box> <Box id="1"> ...

我有以下两层
XML
结构。盒子列表,每个盒子包含一个抽屉列表

<Boxes>
    <Box id="0">
        <Drawers>
            <Drawer id="0"/>
            <Drawer id="1"/>
            ...
        </Drawers>
    </Box>
    <Box id="1">
...
    </Box>
</Boxes>
在那些
迭代器的掩护下
我正在使用
StAX
,它们都在访问相同的底层
XMLStreamReader
。如果调用
BoxIterator.next()
,将影响后续调用
DrawerIterator.next()
时返回的结果,因为光标将移动到下一个框

这是否违反了迭代器的契约? 是否有更好的方法使用
StAX
迭代两级结构

这是否违反了迭代器的契约

没有

Java
迭代器
强加了两个“契约”。第一个契约是Java接口本身,它声明了3个方法:
hasNext()
next()
remove()
。任何实现该
迭代器
接口的类都必须定义这些方法

第二份合同定义了以下人员的行为:

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

这就是全部合同

的确,如果底层的
XMLStreamReader
是高级的,它可能会弄乱您的
BoxIterator
和/或
DrawerIterator
。或者,在错误的点调用
BoxIterator.next()
和/或
DrawerIterator.next()
可能会破坏迭代。但是,正确使用,例如在上面的示例代码中,它可以正常工作并大大简化代码。您只需要记录迭代器的正确用法

作为一个具体的例子,该类实现了迭代器(Iterator),但还有许多其他方法可以推进底层流。如果存在由
迭代器
类强加的更强契约,那么
扫描器
类本身将违反它


正如注释中所指出的,
boxList
不应属于
类BoxIterator实现迭代器,Iterable
。你真的应该:

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

BoxList boxList = ...;
for (Box box : boxList) {
  for (Drawer drawer : box) {
    drawer.getId()
  }
}
在这里,调用两次
boxList.iterator()
,以创建两个独立的
iterator
实例,对框列表进行两次迭代。由于可以多次迭代
boxList
,因此每次迭代都需要一个新的迭代器实例

在代码中:

BoxIterator boxList = new BoxIterator(xml_stream);
for (Box box : boxList) {
  for (Drawer drawer : box) {
    drawer.getId();
  }
}
因为您正在对流进行迭代,所以(不回放流或存储提取的对象)无法再次对相同的节点进行迭代。不需要第二类/对象;同一个对象可以同时作为Iterable和Iterator。。。这将为您保存一个类/对象


话虽如此,过早优化是万恶之源。一个类/对象的节省不值得可能的混淆;您应该将
BoxIterator
拆分为一个
BoxList implements Iterable
BoxIterator implements Iterator
hasNext()
可能返回
true
,但
next()
可能抛出
NoTouchElementException
,因此它有可能破坏契约

hasNext()
的合同是:

如果迭代包含更多元素,则返回true。(换句话说,如果next()将返回元素而不是引发异常,则返回true。)

但是在调用
hasNext()
next()
之间,另一个迭代器可能已经移动了流的位置,这样就没有更多的元素了

但是,按照您使用它的方式(嵌套循环),您不会遇到中断


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

如果您仔细地实现/覆盖
next()
&
hasNext(),它看起来不会破坏契约
通过实现
迭代器
接口,在
框迭代器
抽屉迭代器
中使用方法。不用说,需要注意的明显条件是
hasNext()
应该返回
true
如果
next()
返回一个元素,而
false
如果
next()
给出异常

但我不明白的是,为什么要让
BoxIterator
实现
Iterable

BoxIterator实现Iterator,Iterable

因为从
Iterable
接口重写
Box
iterator()
方法总是会返回
BoxIterator
的实例。如果您没有任何其他目标,那么将此功能封装在
BoxIterator

中是没有意义的。您的代码块的唯一设计问题是
BoxIterator
同时实现了
迭代器和
Iterable
。通常,每次调用
Iterator()
方法时,
Iterable
对象都会返回新的有状态的
Iterator
。正因为如此,两个迭代器之间应该没有干扰,但您需要一个状态对象来正确实现从内部循环退出(可能您已经有了,但为了清楚起见,我必须提到它)

  • 状态对象将充当解析器的代理,有两个方法popEvent和peekEvent。在peek上,迭代器将检查最后一个事件,但不会使用它。在pop上,他们将消耗最后一个事件
  • BoxIterable#iterator()
    将使用StartElement(box),然后返回iterator
  • BoxIterator#hasNext()
    将查看事件并将其弹出,直到收到StartElement或EndElement。只有当Star
    List<Box> boxList = Arrays.asList(box1, box2, box3, box4);
    for(Box box : boxList) {
        // Do something
    }
    for(Box box : boxList) {
        // Do some more stuff
    }
    
    BoxIterator boxList = new BoxIterator(xml_stream);
    for (Box box : boxList) {
      for (Drawer drawer : box) {
        drawer.getId();
      }
    }
    
    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()
      }
    }