2016-07-28 21 views
13

Mam następującą strukturę dwóch poziomów: . Lista pól, z których każda zawiera listę szuflad.Powtórzenie struktury dwupoziomowej przy użyciu zagnieżdżonych iteratorów

<Boxes> 
    <Box id="0"> 
     <Drawers> 
      <Drawer id="0"/> 
      <Drawer id="1"/> 
      ... 
     </Drawers> 
    </Box> 
    <Box id="1"> 
... 
    </Box> 
</Boxes> 

mam parsowania go za pomocą StAX i wystawiony przez strukturę dwóch Iterators:

  1. BoxIterator implements Iterator<Box>, Iterable<Box>
  2. Box implements Iterable<Drawer>
  3. DrawerIterator implements Iterator<Drawer>

mogę następnie wykonaj następujące czynności:

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

Pod maską tych Iterators używam StAX i oba z nich dostęp do tej samej podstawowej XMLStreamReader. Jeśli zadzwonię pod numer BoxIterator.next(), wpłynie to na wynik, który zostanie zwrócony podczas kolejnych wywołań do DrawerIterator.next(), ponieważ kursor zostanie przeniesiony do następnego pola.

Czy to przełamuje umowę z Iterator? Czy istnieje lepszy sposób na iteracje w strukturze dwupoziomowej przy użyciu StAX?

+1

Twój opis wygląda 'Box.iterator' zwraca nowy' DrawerIterator' a jeśli tak jest umowa nie zostanie przerwane, ponieważ 'DrawerIterator' powinien zwracać tylko elementy wewnątrz bieżącego pola. – Thomas

+0

@Thomas 'Box.iterator()' zwróci ten sam 'DrawerIterator' przy każdym wywołaniu, ponieważ i tak wszyscy będą mieli dostęp do tego samego strumienia bazowego. Oznacza to, że nawet 'DrawerIterator' zwrócony przez poprzednie wywołanie' Box.iterator() 'będzie magicznie zaawansowany. Wszyscy będą zawsze uzyskiwać dostęp do podstawowego strumienia w tej samej pozycji kursora. – Roland

+0

Ah Widzę. To by złamało wtedy umowę. Czy musisz zwracać to samo wystąpienie przy każdym połączeniu? Jeśli za każdym razem zwrócisz nową instancję i powtórzysz ją sekwencyjnie (tj. Bez dostępu losowego), nie ma znaczenia, czy pozycja kursora zostałaby przesunięta. Po przejściu przez szuflady w pudełku każde dalsze wywołanie tego pola "DrawerIterator" hasNext() 'powinno zwrócić false. – Thomas

Odpowiedz

5

Czy to łamie umowę z Iterator?

nr

Java Iterator nakłada dwa "kontrakty". Pierwsza umowa to sam interfejs Java, który deklaruje 3 metody: hasNext(), next() i remove(). Każda klasa implementująca ten interfejs Iterator musi zdefiniować te metody.

Druga umowa określa zachowanie Iterator:

hasNext() [...] Zwraca true jeśli iteracji ma więcej elementów. [...] next() zwraca następny element w iteracji [i] wyrzuca NoSuchElementException, jeśli iteracja nie zawiera już żadnych elementów.

To jest cała umowa.

Prawdą jest, że jeśli zaawansowany XMLStreamReader jest zaawansowany, może zepsuć twoje BoxIterator i/lub DrawerIterator. Alternatywnie, wywołanie BoxIterator.next() i/lub DrawerIterator.next() w niewłaściwych punktach może zepsuć iterację. Jednak użyte poprawnie, takie jak w powyższym przykładzie kodu, działa poprawnie i znacznie upraszcza kod. Wystarczy udokumentować poprawne użycie iteratorów.

Jako konkretny przykład, klasa Scanner implementuje Iterator<String>, a mimo to posiada wiele, wiele innych metod, które przesuwają strumień bazowy. Jeśli istniałaby silniejsza umowa narzucona przez klasę Iterator, to sama klasa Scanner naruszałaby ją.


Jako Ivan podkreśla w komentarzach, boxList nie powinny być typu class BoxIterator implements Iterator<Box>, Iterable<Box>. Naprawdę powinien mieć:

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

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

Chociaż mając jedną klasę realizować zarówno Iterable i Iterator nie jest technicznie źle dla przypadku użycia, może powodować zamieszanie.

Rozważmy następujący kod w innym kontekście:

List<Box> boxList = Arrays.asList(box1, box2, box3, box4); 
for(Box box : boxList) { 
    // Do something 
} 
for(Box box : boxList) { 
    // Do some more stuff 
} 

Tutaj boxList.iterator() nazywa się dwa razy, aby utworzyć dwa oddzielne Iterator<Box> instancje, dla iteracji listę skrzynek dwukrotnie. Ponieważ boxList można wielokrotnie powtarzać, każda iteracja wymaga nowej instancji iteratora.

W kodzie:

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

ponieważ jesteś iteracji nad strumieniem, nie można (bez przewijania strumienia lub przechowywania wyodrębnione obiektów) iteracyjne nad tymi samymi węzłami po raz drugi. Druga klasa/obiekt nie jest potrzebna; ten sam obiekt może działać zarówno jako Iterable, jak i Iterator ... co oszczędza ci jedną klasę/obiekt.

Mimo, że przedwczesna optymalizacja jest źródłem wszelkiego zła. Oszczędności jednej klasy/obiektu nie są warte możliwego zamieszania; powinieneś podzielić BoxIterator na BoxList implements Iterable<Box> i BoxIterator implements Iterator<Box>.

+1

W rzeczywistości przykład kodu nie jest tak dobry, ponieważ klasa BoxIterator jest zarówno Iterable, jak i Iterator. Rzeczy mogą stać się nieporządne przy drugim użyciu tej samej instancji, jeśli stan iteratora nie jest resetowany. –

+0

@IvanGammel masz punkt. BoxIterator po prostu zwraca 'this' w wywołaniu' iterator() ', a pozycja kursora na bazowym XMLStreamReader nie jest resetowana. Więc może powinienem po prostu nie używać całego Iteratora, iteracyjnego paradygmatu. Zrobiłem to tylko po to, aby móc użyć ulepszonej pętli for, czyli cukru syntaktycznego. – Roland

+2

@Roland, chociaż nie jest to zwykły sposób analizowania XML, Twój przypadek użycia jest ważny, jeśli dane wejściowe są ogromne i masz mały limit sterty (w przeciwnym razie możesz po prostu przetworzyć cały plik na model obiektowy za pomocą XMLBeans lub XStream), dzięki czemu możesz użyj tego podejścia (dla mnie wygląda to jak wzór Aktywnego Zapisu). Trzeba to tylko ostrożnie wdrożyć. –

3

To ma potencjał, by zerwać kontrakt z powodu, że hasNext() mógł wrócić true, ale next() mógłby rzucić NoSuchElementException.

Umowa hasNext() jest:

Zwraca true jeśli iteracji ma więcej elementów. (Innymi słowy, zwraca true jeśli next() zwróci raczej element niż rzuca wyjątek.)

Ale może się zdarzyć, że między wywołaniem hasNext() i next() kolejna iterator mógł przeniósł pozycję strumienia tak, że nie są już elementami.

Jednak w sposób w jaki go użyłeś (pętla zagnieżdżona), nie napotkasz pęknięcia.

Jeśli było przekazać iterator do innego procesu, to może spotkać ten stłuczeniu.

+0

Problem, który wskazałeś, może się zdarzyć z jakimkolwiek "Iteratorem", nie? Jeśli po wywołaniu 'hasNext()' przekazujesz 'Iterator' do innego procesu, który je zużywa, to' next() 'nie zwróci ci tego, czego oczekiwałeś. – Roland

+1

@Roland Miałem na myśli, że nadanie * innemu * iteratorowi innego procesu może wpłynąć na iterator. Wywołanie 'next()' wpływa na * wszystkie * iteratory, ponieważ mają one wspólne dane wejściowe. – Bohemian

+1

(Prawie) każdy iterator dzieli stan podstawowy z _something_. I nawet gdy 'hasNext()' zwraca 'true', to nie gwarantuje, że' next() ', jeśli zostanie wywołany _ natychmiastowo zaraz potem, zakończy się pomyślnie; może rzucić 'ConcurrentModificationException'. Iteratory to tylko pomocnicy; często są syntaktycznie wygodne, ale nigdy nie gwarantują "nie może być zepsutą lub uszkodzoną iteracją" nad jakąś strukturą. – AJNeufeld

0

To nie wygląda jak byłoby zerwać kontrakt pod warunkiem, że są starannie wykonawczych/nadrzędne next() & hasNext() metod w BoxIterator & DrawerIterator wdrażając Iterator interfejs. Nie trzeba dodawać, że oczywistym warunkiem zachowania ostrożności jest to, że hasNext() powinien zwrócić true, jeśli next() zwróci element i false, jeśli next() podaje wyjątek.

Ale to, czego nie mógł zrozumieć, dlaczego nie zrobiłeś BoxIterator wdrożyć Iterable<Box>

BoxIterator implements Iterator<Box>, Iterable<Box> Ponieważ nadrzędnym iterator() metodę z Iterable interfejs dla Box zawsze będzie zwracać instancję BoxIterator. Jeśli nie masz żadnego innego celu, nie ma potrzeby zamykania tej funkcji w BoxIterator.

2

Jedynym problemem z twoim kodem jest to, że BoxIterator implementuje zarówno Iterator, jak i Iterable. Zwykle obiekt Iterable zwraca nowy stanowy Iterator za każdym razem, gdy wywoływana jest metoda iterator(). Z tego powodu nie powinno być żadnych interferencji między dwoma iteratorami, ale będziesz potrzebował obiektu stanu, aby poprawnie zaimplementować wyjście z wewnętrznej pętli (prawdopodobnie już to masz, ale muszę o tym wspomnieć dla jasności).

  1. Obiekt stanu będzie działał jak proxy dla parsera za pomocą dwóch metod: popEvent i peekEvent. W przypadku podglądu, iteratory sprawdzą ostatnie wydarzenie, ale go nie zużyją. na pop, będą konsumować ostatnie wydarzenie.
  2. BoxIterable#iterator() zużyje StartElement (pola) i zwróci iterator po tym.
  3. BoxIterator#hasNext() będzie wyświetlać zdarzenia i wyświetlać je do momentu otrzymania wartości StartElement lub EndElement. Następnie zwróci true tylko wtedy, gdy odebrano StartElement (Box).
  4. BoxIterator#next() będzie peek-and-pop Atrybutuj zdarzenia do otrzymania informacji StartElement lub EndElement w celu zainicjowania obiektu Box.
  5. Box#iterator() pochłonie wydarzenie StartElement (Szuflady), a następnie zwróci DrawerIterator.
  6. DrawerIterator#hasNext() będzie peek-and-pop do otrzymania StartElement lub EndElement. Następnie zwróci true tylko wtedy, gdy był StartElement (Szuflada)
  7. DrawerIterator#next() będzie zużywać Attribute zdarzenia aż EndElement (szuflada) otrzymał.

kod użytkownika pozostanie prawie niezmodyfikowanej:

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

_Odnotalnie obiekt Iterable zwraca nowy stanowy iterator za każdym razem, gdy wywoływana jest metoda iterator(). Tak nie jest w tym przypadku. 'BoxIterator' ma jeden bazowy' XMLStreamReader', więc zwracam tylko 'this' w metodzie' iterator() '. Stan tego Iteratora będzie stanowił położenie kursora w strumieniu bazowym. – Roland

Powiązane problemy