2016-07-28 1 views
13

У меня есть следующая структура уровня 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>

, то я могу сделать следующее:

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

Под капотом этих Iterators я использую StAX и оба из них обращается к тому же основному XMLStreamReader. Если я вызову BoxIterator.next(), это повлияет на результат, который будет возвращен при последующих вызовах DrawerIterator.next(), потому что курсор переместится в следующее окно.

Означает ли это расторжение договора с Iterator? Есть ли лучший способ итерации над структурой двух уровней, используя StAX?

+1

Ваше описание выглядит как 'Box.iterator' возвращает новый' DrawerIterator' и если это так, то договор не будет нарушен, так как 'DrawerIterator' должен возвращать только элементы внутри текущего поля. – Thomas

+0

@Thomas 'Box.iterator()' будет возвращать тот же самый DrawerIterator для каждого вызова, так как все они будут иметь доступ к одному и тому же базовому потоку. Это означает, что даже «DrawerIterator», возвращенный прошлым вызовом 'Box.iterator()', будет магически продвинут. Все будут получать доступ к базовому потоку в одной позиции курсора, всегда. – Roland

+0

А я вижу. Тогда это нарушит контракт. Нужно ли возвращать один и тот же экземпляр при каждом вызове? Если вы каждый раз возвращаете новый экземпляр и последовательно повторяете (то есть произвольный доступ), не имеет значения, была ли указана позиция курсора. После того, как вы выполнили итерацию над ящиками коробки, дальнейший вызов этого поля «Исключить (hasNext)) DrawerIterator должен возвращать значение false. – Thomas

ответ

5

Означает ли это расторжение договора 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>.

+1

На самом деле, пример кода не очень хорош, потому что класс BoxIterator является как Iterable, так и Iterator. Вещи могут стать беспорядочными при повторном использовании одного и того же экземпляра, если состояние итератора не сбрасывается. –

+0

@IvanGammel у вас есть точка. BoxIterator просто возвращает 'this' в вызове' iterator() ', а позиция курсора на базовом XMLStreamReader не сбрасывается. Поэтому, возможно, мне не следует использовать всю итераторную парадигму Iterable. Я сделал это только для того, чтобы использовать расширенный цикл for, т. Е. Синтаксический сахар. – Roland

+2

@Roland, в то время как это не обычный способ разобрать XML, ваш прецедент действителен, если входной файл огромен, и у вас ограничен небольшой размер кучи (иначе вы могли бы просто разобрать весь файл на объектную модель с помощью XMLBeans или XStream), так что вы можете используйте этот подход (для меня это выглядит как шаблон Active Record). Вам просто нужно тщательно ее реализовать. –

3

Он имеет потенциал, чтобы разорвать контракт по той причине, что hasNext() может вернуться true, но next() может кинуть NoSuchElementException.

Договор hasNext() является:

Возвращает истину, если итерации имеет больше элементов. (Другими словами, возвращает истину, если в следующем() возвращает элемент, а не бросать исключение.)

Но может случиться так, что между вызовом hasNext() и next(), другой итератор мог бы переместил положение потока таким образом, что больше нет элементов.

Однако, поскольку вы использовали его (вложенный цикл), вы не столкнетесь с поломкой.

Если вам нужно передать итератор другому процессу, вы можете столкнуться с этим поломкой.

+0

Проблема, которую вы указали, может произойти с любым «Итератором», нет? Если после вызова 'hasNext()' вы передаете 'Iterator' другому процессу, который его использует, то' next() 'не вернет вам то, что вы ожидали. – Roland

+1

@Roland Я имел в виду, что предоставление * другого * итератора другому процессу может повлиять на итератор. Вызов 'next()' влияет на все * итераторы, потому что они имеют один и тот же базовый ввод. – Bohemian

+1

(Почти) каждый итератор разделяет основное состояние с _something_. И даже если 'hasNext()' возвращает 'true', это не гарантирует, что' next() ', если он будет вызван _ сразу после этого_, будет _allways_ успешным; это может вызвать «ConcurrentModificationException». Итераторы - просто помощники; они часто синтаксически удобны, но они никогда не гарантируют, что «не может быть сломана или испорчена итерация» над некоторой структурой. – AJNeufeld

0

Это не выглядит, как она будет разорвать контракт при условии вы тщательно реализации/переопределение next() & hasNext() методы в BoxIterator & DrawerIterator путем внедрения Iterator интерфейса. Само собой разумеется, очевидным условием для ухода является то, что hasNext() должен вернуть true, если next() возвращает элемент и false, если next() предоставляет исключение.

Но то, что я не мог понять, почему вы сделали BoxIterator реализовать Iterable<Box>

BoxIterator implements Iterator<Box>, Iterable<Box> Поскольку переопределение iterator() метод из Iterable интерфейса для Box всегда будет возвращать экземпляр BoxIterator. Если у вас нет других целей, то нет цели инкапсуляции этой функции в BoxIterator.

2

Единственная проблема с вашей частью кода заключается в том, что BoxIterator реализует как Iterator, так и Iterable. Обычно Iterable объект возвращает новый stateful Iterator каждый раз, когда вызывается метод iterator(). Из-за этого не должно быть никаких помех между двумя итераторами, но вам понадобится объект состояния, чтобы правильно реализовать выход из внутреннего цикла (возможно, у вас уже есть это, но я должен упомянуть его для ясности).

  1. Объект State будет действовать как прокси-сервер для анализатора с двумя методами popEvent и peekEvent. На иетерах peek проверит последнее событие, но не будет его использовать. поп, они будут использовать последнее событие.
  2. BoxIterable#iterator() будет использовать StartElement (Boxes) и после этого вернуть итератор.
  3. BoxIterator#hasNext() будет заглядывать в события и всплывать до тех пор, пока не будет получен StartElement или EndElement. Он будет возвращен true, только если был получен StartElement (Box).
  4. BoxIterator#next() будет заглядывать в атрибуты событий до появления элемента StartElement или EndElement для инициализации объекта Box.
  5. Box#iterator() будет использовать событие StartElement (Ящики), а затем вернуть DrawerIterator.
  6. DrawerIterator#hasNext() будет заглядывать и запускаться до появления элемента StartElement или EndElement. Затем он вернет true, только если он был StartElement (Drawer)
  7. 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() 
    } 
} 
+0

_Normally, Iterable object возвращает новый итератор с сохранением состояния каждый раз, когда вызывается метод iterator(). Это не тот случай. 'BoxIterator' имеет один базовый' XMLStreamReader', поэтому я возвращаю 'this' только в методе' iterator() '. Состояние этого Итератора будет тем местом, где курсор находится на базовом потоке. – Roland

Смежные вопросы