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>
.
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
@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
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