使用嵌套迭代器迭代两级结构
Iterating over a two level structure using nested iterators
我有以下两级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
遍历两级结构?
Does this break the contract of Iterator
?
没有
Java Iterator
强加两个"contracts"。第一个契约是 Java 接口本身,它声明了 3 个方法:hasNext()
、next()
和 remove()
。任何实现此 Iterator
接口的 class 都必须定义这些方法。
第二个合约定义了Iterator
的行为:
hasNext()
[...] returns true if the iteration has more elements. [...] next()
returns the next element in the iteration [and] throws NoSuchElementException
if the iteration has no more elements.
这就是整个合同。
的确,如果基础XMLStreamReader
超前,它会把你的BoxIterator
and/orDrawerIterator
搞得一团糟。或者,在错误的点调用 BoxIterator.next()
and/or DrawerIterator.next()
可能会搞乱迭代。但是,正确使用,例如在您上面的示例代码中,它可以正常工作并大大简化代码。您只需要记录迭代器的正确用法。
作为一个具体的例子,Scanner
class 实现了 Iterator<String>
,但还有许多其他方法可以推进底层流。如果 Iterator
class 强加了更强的契约,那么 Scanner
class 本身就会违反它。
正如 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()
}
}
虽然让一个 class 同时实现 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();
}
}
因为您正在迭代一个流,所以您不能(不倒回流或存储提取的对象)第二次迭代相同的节点。不需要第二个 class/object;同一个对象可以同时充当 Iterable 和 Iterator ...这为您节省了一个 class/object.
话说回来,过早的优化是万恶之源。一个 class/object 的节省不值得可能的混乱;你应该把 BoxIterator
分成 BoxList implements Iterable<Box>
和 BoxIterator implements Iterator<Box>
.
它有可能违约,因为 hasNext()
可以 return true
,但 next()
可以抛出 NoSuchElementException
。
hasNext()
的合约是:
Returns true if the iteration has more elements. (In other words, returns true if next() would return an element rather than throwing an exception.)
但也有可能在调用 hasNext()
和 next()
之间,另一个迭代器可能移动了流的位置,以至于不再有元素。
但是,按照您使用它的方式(嵌套循环),您不会遇到破损。
如果您要将迭代器传递给另一个进程,那么您可能会遇到这种破坏。
如果您仔细 implementing/overriding next()
& hasNext()
BoxIterator
& DrawerIterator
中的方法,它看起来不会破坏合同通过实现 Iterator
接口。不用说,要注意的明显条件是 hasNext()
应该 return true
如果 next()
是 returning 元素,false
如果next()
给出了例外。
但是我不明白的是你为什么要BoxIterator
实现Iterable<Box>
BoxIterator implements Iterator<Box>, Iterable<Box>
由于从 Box
的 Iterable
接口覆盖 iterator()
方法总是会 return BoxIterator
的一个实例。如果你后面没有其他的objective,那么把这个特性封装在BoxIterator
.
中就没有意义了
您的代码的唯一设计问题是 BoxIterator
同时实现了 Iterator
和 Iterable
。通常,每次调用 iterator()
方法时,Iterable
对象 return 都会有新的有状态 Iterator
。因此,两个迭代器之间应该没有干扰,但是您需要一个状态对象来正确实现从内部循环退出(可能,您已经有了,但为了清楚起见,我必须提及它)。
- State 对象将充当具有 popEvent 和 peekEvent 两种方法的解析器的代理。 On peek 迭代器将检查最后一个事件,但不会消耗它。在弹出时,他们将消耗最后一个事件。
BoxIterable#iterator()
之后将消耗 StartElement(Boxes) 和 return 迭代器。
BoxIterator#hasNext()
将查看事件并弹出它们,直到接收到 StartElement 或 EndElement。只有当接收到 StartElement(Box) 时,它才会 return 为真。
BoxIterator#next()
将查看并弹出属性事件,直到收到 StartElement 或 EndElement 以初始化 Box 对象。
Box#iterator()
将消耗 StartElement(Drawers) 事件,然后 return DrawerIterator.
DrawerIterator#hasNext()
将偷看并弹出,直到收到 StartElement 或 EndElement。只有当它是 StartElement(Drawer) 时,它才会 return 为真
DrawerIterator#next()
将消耗属性事件,直到接收到 EndElement(Drawer)。
您的用户代码将几乎保持不变:
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()
}
}
我有以下两级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
遍历两级结构?
Does this break the contract of
Iterator
?
没有
Java Iterator
强加两个"contracts"。第一个契约是 Java 接口本身,它声明了 3 个方法:hasNext()
、next()
和 remove()
。任何实现此 Iterator
接口的 class 都必须定义这些方法。
第二个合约定义了Iterator
的行为:
hasNext()
[...] returns true if the iteration has more elements. [...]next()
returns the next element in the iteration [and] throwsNoSuchElementException
if the iteration has no more elements.
这就是整个合同。
的确,如果基础XMLStreamReader
超前,它会把你的BoxIterator
and/orDrawerIterator
搞得一团糟。或者,在错误的点调用 BoxIterator.next()
and/or DrawerIterator.next()
可能会搞乱迭代。但是,正确使用,例如在您上面的示例代码中,它可以正常工作并大大简化代码。您只需要记录迭代器的正确用法。
作为一个具体的例子,Scanner
class 实现了 Iterator<String>
,但还有许多其他方法可以推进底层流。如果 Iterator
class 强加了更强的契约,那么 Scanner
class 本身就会违反它。
正如 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()
}
}
虽然让一个 class 同时实现 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();
}
}
因为您正在迭代一个流,所以您不能(不倒回流或存储提取的对象)第二次迭代相同的节点。不需要第二个 class/object;同一个对象可以同时充当 Iterable 和 Iterator ...这为您节省了一个 class/object.
话说回来,过早的优化是万恶之源。一个 class/object 的节省不值得可能的混乱;你应该把 BoxIterator
分成 BoxList implements Iterable<Box>
和 BoxIterator implements Iterator<Box>
.
它有可能违约,因为 hasNext()
可以 return true
,但 next()
可以抛出 NoSuchElementException
。
hasNext()
的合约是:
Returns true if the iteration has more elements. (In other words, returns true if next() would return an element rather than throwing an exception.)
但也有可能在调用 hasNext()
和 next()
之间,另一个迭代器可能移动了流的位置,以至于不再有元素。
但是,按照您使用它的方式(嵌套循环),您不会遇到破损。
如果您要将迭代器传递给另一个进程,那么您可能会遇到这种破坏。
如果您仔细 implementing/overriding next()
& hasNext()
BoxIterator
& DrawerIterator
中的方法,它看起来不会破坏合同通过实现 Iterator
接口。不用说,要注意的明显条件是 hasNext()
应该 return true
如果 next()
是 returning 元素,false
如果next()
给出了例外。
但是我不明白的是你为什么要BoxIterator
实现Iterable<Box>
BoxIterator implements Iterator<Box>, Iterable<Box>
由于从 Box
的 Iterable
接口覆盖 iterator()
方法总是会 return BoxIterator
的一个实例。如果你后面没有其他的objective,那么把这个特性封装在BoxIterator
.
您的代码的唯一设计问题是 BoxIterator
同时实现了 Iterator
和 Iterable
。通常,每次调用 iterator()
方法时,Iterable
对象 return 都会有新的有状态 Iterator
。因此,两个迭代器之间应该没有干扰,但是您需要一个状态对象来正确实现从内部循环退出(可能,您已经有了,但为了清楚起见,我必须提及它)。
- State 对象将充当具有 popEvent 和 peekEvent 两种方法的解析器的代理。 On peek 迭代器将检查最后一个事件,但不会消耗它。在弹出时,他们将消耗最后一个事件。
BoxIterable#iterator()
之后将消耗 StartElement(Boxes) 和 return 迭代器。BoxIterator#hasNext()
将查看事件并弹出它们,直到接收到 StartElement 或 EndElement。只有当接收到 StartElement(Box) 时,它才会 return 为真。BoxIterator#next()
将查看并弹出属性事件,直到收到 StartElement 或 EndElement 以初始化 Box 对象。Box#iterator()
将消耗 StartElement(Drawers) 事件,然后 return DrawerIterator.DrawerIterator#hasNext()
将偷看并弹出,直到收到 StartElement 或 EndElement。只有当它是 StartElement(Drawer) 时,它才会 return 为真
DrawerIterator#next()
将消耗属性事件,直到接收到 EndElement(Drawer)。
您的用户代码将几乎保持不变:
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()
}
}