Означает ли это расторжение договора Iterator
?
No.
Java-Iterator
накладывает два "контракты". Первым контрактом является сам интерфейс Java, который объявляет 3 метода: hasNext()
, next()
и remove()
. Любой класс, который реализует этот интерфейс Iterator
, должен определить эти методы.
Второй контракт определяет поведение Iterator
:
hasNext()
[...] возвращает истину, если итерации имеет больше элементов. [...] 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>
.
Ваше описание выглядит как 'Box.iterator' возвращает новый' DrawerIterator' и если это так, то договор не будет нарушен, так как 'DrawerIterator' должен возвращать только элементы внутри текущего поля. – Thomas
@Thomas 'Box.iterator()' будет возвращать тот же самый DrawerIterator для каждого вызова, так как все они будут иметь доступ к одному и тому же базовому потоку. Это означает, что даже «DrawerIterator», возвращенный прошлым вызовом 'Box.iterator()', будет магически продвинут. Все будут получать доступ к базовому потоку в одной позиции курсора, всегда. – Roland
А я вижу. Тогда это нарушит контракт. Нужно ли возвращать один и тот же экземпляр при каждом вызове? Если вы каждый раз возвращаете новый экземпляр и последовательно повторяете (то есть произвольный доступ), не имеет значения, была ли указана позиция курсора. После того, как вы выполнили итерацию над ящиками коробки, дальнейший вызов этого поля «Исключить (hasNext)) DrawerIterator должен возвращать значение false. – Thomas