"Cześć, Amigo!"

"Cześć, Ellie!"

„Dzisiaj chcę opowiedzieć o iteratorach”.

„Iteratory zostały wynalezione praktycznie w tym samym czasie co kolekcje. Głównym celem kolekcji jest przechowywanie elementów, a głównym celem iteratora jest pobieranie tych elementów jeden po drugim”.

„Co jest takiego trudnego w zdobyciu zestawu elementów?”

„Po pierwsze, elementy w niektórych kolekcjach, takich jak Set, nie mają ustalonej kolejności i/lub kolejność stale się zmienia”.

„Po drugie, niektóre struktury danych mogą przechowywać obiekty w bardzo złożony sposób: w różnych grupach, listach itp. Innymi słowy, rozdanie wszystkich elementów w kolejności byłoby nietrywialnym zadaniem”.

„Po trzecie, kolekcje mają tendencję do zmian. Załóżmy, że zdecydujesz się wyświetlić całą zawartość kolekcji, ale w samym środku danych wyjściowych JVM przełącza się na inny wątek, który zastępuje połowę elementów kolekcji. Więc zamiast danych wyjściowych otrzymujesz kto wie co”.

"Hmm..."

„Ale! Właśnie takie problemy może rozwiązać iterator. Iterator to specjalny obiekt w kolekcji, który z jednej strony ma dostęp do wszystkich swoich prywatnych danych i zna swoją wewnętrzną strukturę, a z drugiej strony , implementuje publiczny interfejs Iterator, dzięki któremu wszyscy wiedzą, jak z nim pracować.

„Niektóre iteratory mają wewnętrzną tablicę, do której kopiowane są wszystkie elementy kolekcji podczas tworzenia iteratora. Dzięki temu wszelkie późniejsze zmiany w kolekcji nie wpłyną na liczbę ani kolejność elementów”.

„Myślę, że natknąłeś się na to podczas pracy z for each . Nie możesz jednocześnie zapętlać kolekcji i usuwać z niej elementów. Wszystko to wynika właśnie ze sposobu działania iteratora”.

„W nowych kolekcjach dodanych do biblioteki współbieżności iterator jest przerabiany w celu wyeliminowania tego problemu”.

„Przypomnę, jak działa iterator”.

„Java ma specjalny interfejs Iteratora. Oto jego metody:”

Metody interfejsu Iterator<E> Opis
boolean hasNext() Sprawdza, czy są jeszcze jakieś elementy
E next() Zwraca bieżący element i przechodzi do następnego.
void remove() Usuwa bieżący element

„Iterator pozwala sukcesywnie pobierać wszystkie elementy kolekcji. Bardziej logiczne jest myślenie o iteratorze jako o czymś w rodzaju InputStream — ma on wszystkie dane, ale jego zadaniem jest sekwencyjne wyprowadzanie”.

„   Metoda next () zwraca następny element w kolekcji”.

„ Metoda hasNext () służy do sprawdzania, czy są jeszcze jakieś elementy”.

„I usuń () usuwa bieżący element”.

"Jakieś pytania?"

„Dlaczego metody mają takie dziwne nazwy? Dlaczego nie isEmpty() i getNextElement()?”

– Czy to nie miałoby większego sensu?

„Miałoby to większy sens, ale nazwy pochodzą z języka C++, w którym wcześniej pojawiły się iteratory”.

- Rozumiem. Kontynuujmy.

„Oprócz iteratora istnieje również interfejs Iterable, który musi być zaimplementowany przez wszystkie kolekcje obsługujące iteratory. Ma jedną metodę:”

Metody interfejsu Iterable<T> Opis
Iterator<T>iterator() Zwraca obiekt iteratora

„Możesz użyć tej metody na dowolnej kolekcji, aby obiekt iteratora przeszedł przez jej elementy. Przejdźmy przez wszystkie elementy w TreeSet :

Przykład
TreeSet<String> set = new TreeSet<String>();
Iterator<String> iterator = set.iterator();

while (iterator.hasNext())
{
 String item = iterator.next();
 System.out.println(item);
}

„Używanie takiego iteratora nie jest zbyt wygodne — jest zbyt dużo zbędnego i oczywistego kodu. Sytuacja stała się prostsza, gdy w Javie pojawiła się pętla for-each ”.

„Teraz ten kod jest znacznie bardziej zwarty i czytelny:”

Zanim Po
TreeSet<String> set = new TreeSet<String>();
Iterator<String> iterator = set.iterator();

while (iterator.hasNext())
{
 String item = iterator.next();
 System.out.println(item);
}
TreeSet<String> set = new TreeSet<String>();

for(String item : set)
{
 System.out.println(item);
}

„To jest ten sam kod! Iterator jest używany w obu przypadkach”.

„Po prostu jego użycie jest ukryte w pętli for-each . Zauważ, że kod po prawej stronie nie ma w ogóle czerwonego tekstu. Użycie iteratora jest całkowicie ukryte”.

„ Pętla for-each może być używana dla dowolnych obiektów obsługujących iteratory. Innymi słowy, możesz napisać własną klasę, dodać do niej metodę iterator () i użyć jej obiektów w konstrukcji for-each ”.

„Wow! Oczywiście nie mam ochoty pisać własnych kolekcji i iteratorów, ale perspektywa wciąż jest kusząca. Zanotuję to”.

Ponadto istnieje inny popularny typ iteratora, który ma nawet własny interfejs. Mówię o iteratorze dla list, czyli ListIterator .

„Niezależnie od implementacji, listy zachowują kolejność elementów, co sprawia, że ​​praca z nimi przez iterator jest nieco wygodniejsza”.

„Oto metody interfejsu ListIterator <E>:”

metoda Opis
boolean hasNext() Sprawdza, czy przed nami są jeszcze jakieś elementy.
E next() Zwraca następny element.
int nextIndex() Zwraca indeks następnego elementu
void set(E e) Zmienia wartość bieżącego elementu
boolean hasPrevious() Sprawdza, czy z tyłu znajdują się jakieś elementy.
E previous() Zwraca poprzedni element
int previousIndex() Zwraca indeks poprzedniego elementu
void remove() Usuwa bieżący element
void add(E e) Dodaje element na końcu listy.

„Innymi słowy, tutaj możemy poruszać się zarówno do przodu, jak i do tyłu. Jest też kilka innych drobnych funkcji”.

„Cóż, to ciekawe. Gdzie się tego używa?”

„Załóżmy, że chcesz poruszać się tam iz powrotem po połączonej liście. Operacja get będzie raczej powolna, ale operacja next() będzie bardzo szybka”.

- Hmm. Przekonałeś mnie. Zapamiętam to.

— Dzięki, Ellie!