我有以下两级XML
结构。一个方框列表,每个方框都包含一个抽屉列表。
<Boxes>
<Box id="0">
<Drawers>
<Drawer id="0"/>
<Drawer id="1"/>
...
</Drawers>
</Box>
<Box id="1">
...
</Box>
</Boxes>
我正在使用StAX
对其进行解析,并通过两个Iterators
公开结构:
BoxIterator implements Iterator<Box>, Iterable<Box>
Box implements Iterable<Drawer>
DrawerIterator implements Iterator<Drawer>
然后我可以执行以下操作:
BoxIterator boxList;
for (Box box : boxList) {
for (Drawer drawer : box) {
drawer.getId()
}
}
使用Iterators
StAX
XMLStreamReader
,我们两人正在访问相同的基础BoxIterator.next()
。如果我调用DrawerIterator.next()
,它将影响后续调用Iterator
时返回的结果,因为光标将移动到下一个框。
这会违反StAX
的合同吗?
有没有更好的方法使用equals
迭代两级结构?
答案 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()
}
}
虽然从一个类实现Iterable
和Iterator
对于您的用例在技术上并不是错误的,但它可能会导致混淆。
在另一个上下文中考虑此代码:
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
同时实现Iterator
和Iterable
。通常,Iterable
对象每次调用Iterator
方法时都会返回新的有状态iterator()
。因此,两个迭代器之间不应该存在干扰,但是你需要一个状态对象才能正确地实现从内循环中退出(可能你已经有了这个,但为了清楚起见,我必须提一下)。
BoxIterable#iterator()
将使用StartElement(Boxes)并在此之后返回迭代器。BoxIterator#hasNext()
将查看事件并弹出它们,直到收到StartElement或EndElement。只有在收到StartElement(Box)时才会返回true。BoxIterator#next()
将查看并弹出属性事件,直到收到StartElement或EndElement来初始化Box对象。Box#iterator()
将使用StartElement(Drawers)事件,然后返回DrawerIterator。DrawerIterator#hasNext()
将一直闪烁,直到收到StartElement或EndElement。只有当它是StartElement(Drawer)DrawerIterator#next()
将使用属性事件,直到收到EndElement(抽屉)。您的用户代码几乎不会被修改:
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}}中封装此功能。