Dzisiaj porozmawiamy o tym, czym jest Iterator w Javie i dlaczego jest potrzebny.
Jak zapewne już wiesz, Java ma wspaniały interfejs Collection, który implementuje interfejs Iterator. Pozwólcie, że od razu dokonam rezerwacji: interfejsu iteratora nie należy mylić ze wzorcem iteratora w Javie! Aby to wyjaśnić, spójrzmy najpierw na interfejs.
Wyobraźmy sobie na chwilę, że w Javie nie ma iteratora. W takim przypadku każdy będzie musiał zanurzyć się w głąb kolekcji i naprawdę zrozumieć, czym się różni
Dosłownie „Iterator” można przetłumaczyć jako „brutalna siła ” . Oznacza to, że jest to pewna jednostka, która może iterować po wszystkich elementach kolekcji. Co więcej, pozwala to zrobić bez zagłębiania się w wewnętrzną strukturę i układ zbiorów. |
ArrayList
od LinkedList
i HashSet
od TreeSet
.
Metody, które Iterator musi wdrożyć
boolean hasNext()
— jeśli w obiekcie iterowalnym pozostały jeszcze wartości (obecnie Kolekcja), metoda zwróci true
, jeśli nie ma już więcej wartości false
. E next()
— zwraca kolejny element kolekcji (obiekt). Jeżeli nie ma już więcej elementów (nie było checku hasNext()
i wywołaliśmy next()
kiedy doszliśmy do końca kolekcji), metoda wyrzuci NoSuchElementException
. void remove()
- usunie element, który został ostatnio uzyskany przez next()
. Metoda może rzucić:
UnsupportedOperationException
, jeśli ten iterator nie obsługuje metodyremove()
(na przykład w przypadku kolekcji tylko do odczytu)IllegalStateException
, jeśli metodanext()
nie została jeszcze wywołana lub zostałaremove()
już wywołana od ostatniego wywołanianext()
.
for-each
. Jego rozszerzeniem jest ListIterator. Przyjrzyjmy się dodatkowym metodom iteratora list Java. Najprawdopodobniej je znasz:
void add(E e)
— wstawia elementE
do ;List
boolean hasPrevious()
— zwrócitrue
, jeśliList
podczas wyszukiwania wstecznego pojawią się elementy;int nextIndex()
— zwróci indeks kolejnego elementu;E previous()
— zwróci poprzedni element arkusza;int previousIndex()
— zwróci indeks poprzedniego elementu;void set(E e)
- zastąpi element zwrócony przez ostatnie wywołanienext()
lubprevious()
elemente
.
List
plik zawierający wiersze pozdrowień dla uczniów:
List<String> list = new ArrayList<>();
list.add("Cześć");
list.add("Обучающимся");
list.add("На");
list.add("JavaRush");
Teraz zdobędziemy dla niego iterator i wypiszemy wszystkie zawarte w nim linie do konsoli:
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
Teraz pojawi się „wąskie gardło”: Kolekcje Java, jak zapewne wiesz (a jeśli nie wiesz, to rozwiąż), rozbuduj interfejs Iterable
, ale to nie znaczy tylko List
, Set
i Queue
obsługuj iterator. Funkcja For java Map iterator
jest również obsługiwana, ale należy ją wywołać Map.entrySet()
:
Map<String, Integer> map = new HashMap<>();
Iterator mapIterator = map.entrySet().iterator();
Następnie metoda next()
zwróci obiekt Entry
zawierający parę „klucz” – „wartość”. Następnie wszystko jest takie samo z List
:
while (mapIterator.hasNext()) {
Map.Entry<String, Integer> entry = mapIterator.next();
System.out.println("Key: " + entry.getKey());
System.out.println("Value: " + entry.getValue());
}
Myślisz: „Przestań. Mówimy o interfejsie, a tytuł artykułu brzmi „Wzór”. Oznacza to, że wzorcem iteratora jest interfejs Iteratora? A może interfejs jest wzorcem? Jeśli to słowo pojawia się po raz pierwszy, podam ci odniesienie: wzorzec jest wzorcem projektowym, pewnym zachowaniem, którego musi przestrzegać klasa lub wiele powiązanych ze sobą klas. Iterator w Javie można zaimplementować dla dowolnego obiektu, którego wewnętrzna struktura wymaga iteracji, można też zmienić sygnaturę omawianych metod. Najważniejszą rzeczą przy wdrażaniu wzorca jest logika, której klasa musi przestrzegać. Interfejs iteratora jest prywatną implementacją wzorca o tej samej nazwie, stosowaną zarówno do gotowych struktur ( List, Set, Queue, Map
), jak i do innych, według uznania programisty. Rozszerzając interfejs Iteratora, implementujesz wzorzec, ale nie musisz rozszerzać interfejsu, aby zaimplementować wzorzec. Prosta analogia: wszystkie ryby pływają, ale nie wszystko, co pływa, jest rybą. Jako przykład zdecydowałem się wziąć... słowo. A dokładniej rzeczownik. Składa się z części: przedrostka, rdzenia, przyrostka i zakończenia. Dla części słowa utworzymy interfejs WordPart
i klasy, które go rozszerzają Prefix, Root, Suffix и Ending
:
interface WordPart {
String getWordPart();
}
static class Root implements WordPart {
private String part;
public Root(String part) {
this.part = part;
}
@Override
public String getWordPart() {
return part;
}
}
static class Prefix implements WordPart {
private String part;
public Prefix(String part) {
this.part = part;
}
@Override
public String getWordPart() {
return part;
}
}
static class Suffix implements WordPart {
private String part;
public Suffix(String part) {
this.part = part;
}
@Override
public String getWordPart() {
return part;
}
}
static class Ending implements WordPart {
private String part;
public Ending(String part) {
this.part = part;
}
@Override
public String getWordPart() {
return part;
}
}
Wtedy klasa Word
(słowo) będzie zawierać części, a oprócz nich dodamy liczbę całkowitą odzwierciedlającą liczbę części w słowie:
public class Word {
private Root root;
private Prefix prefix;
private Suffix suffix;
private Ending ending;
private int partCount;
public Word(Root root, Prefix prefix, Suffix suffix, Ending ending) {
this.root = root;
this.prefix = prefix;
this.suffix = suffix;
this.ending = ending;
this.partCount = 4;
}
public Word(Root root, Prefix prefix, Suffix suffix) {
this.root = root;
this.prefix = prefix;
this.suffix = suffix;
this.partCount = 3;
}
public Word(Root root, Prefix prefix) {
this.root = root;
this.prefix = prefix;
this.partCount = 2;
}
public Word(Root root) {
this.root = root;
this.partCount = 1;
}
public Root getRoot() {
return root;
}
public Prefix getPrefix() {
return prefix;
}
public Suffix getSuffix() {
return suffix;
}
public Ending getEnding() {
return ending;
}
public int getPartCount() {
return partCount;
}
public boolean hasRoot() {
return this.root != null;
}
public boolean hasPrefix() {
return this.prefix != null;
}
public boolean hasSuffix() {
return this.suffix != null;
}
public boolean hasEnding() {
return this.ending != null;
}
OK, mamy cztery przeciążone konstruktory (dla uproszczenia załóżmy, że możemy mieć tylko jeden przyrostek). Rzeczownik nie może składać się z jednego przedrostka, dlatego dla konstruktora z jednym parametrem ustalimy korzeń. Napiszmy teraz implementację wzorca iteratora: WordIterator, nadpisując 2 metody: hasNext()
oraz next()
:
public class WordIterator implements Iterator<Word.WordPart> {
private Word word;
private int wordPartsCount;
public WordIterator(Word word) {
this.word = word;
this.wordPartsCount = word.getPartCount();
}
@Override
public boolean hasNext() {
if (wordPartsCount == 4) {
return word.hasPrefix() || word.hasRoot() || word.hasSuffix() || word.hasEnding();
} else if (wordPartsCount == 3) {
return word.hasPrefix() || word.hasRoot() || word.hasSuffix();
} else if (wordPartsCount == 2) {
return word.hasPrefix() || word.hasRoot();
} else if (wordPartsCount == 1) {
return word.hasRoot();
}
return false;
}
@Override
public Word.WordPart next() throws NoSuchElementException {
if (wordPartsCount <= 0) {
throw new NoSuchElementException("No more elements in this word!");
}
try {
if (wordPartsCount == 4) {
return word.getEnding();
}
if (wordPartsCount == 3) {
return word.getSuffix();
}
if (wordPartsCount == 2) {
return word.getPrefix();
}
return word.getRoot();
} finally {
wordPartsCount--;
}
}
}
Pozostaje tylko przypisać iterator do klasy Word
:
public class Word implements Iterable<Word.WordPart> {
…
@Override
public Iterator<WordPart>iterator() {
return new WordIterator(this);
}
…
}
Przeprowadźmy teraz analizę morfemiczną słowa „rush”:
public class Main {
public static void main(String[] args) {
Word.Root root = new Word.Root("беж");
Word.Prefix prefix = new Word.Prefix("пере");
Word.Suffix suffix = new Word.Suffix("к");
Word.Ending ending = new Word.Ending("a");
Word word = new Word(root, prefix, suffix, ending);
Iterator wordIterator = word.iterator();
while (wordIterator.hasNext()) {
Word.WordPart part = (Word.WordPart) wordIterator.next();
System.out.println(part.getClass() + ": " + part.getWordPart());
}
}
}
Należy pamiętać, że w mojej implementacji wzorca iteratora wybrałem następującą kolejność wyników:
- kończący się
- przyrostek
- konsola
- źródło
GO TO FULL VERSION