JavaRush /Blog Java /Random-PL /Wzór iteratora

Wzór iteratora

Opublikowano w grupie Random-PL
Dzisiaj porozmawiamy o tym, czym jest Iterator w Javie i dlaczego jest potrzebny.
Wzór iteratora - 1
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.
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.
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 ArrayListod LinkedListi HashSetod 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 metody remove()(na przykład w przypadku kolekcji tylko do odczytu)
  • IllegalStateException, jeśli metoda next()nie została jeszcze wywołana lub została remove()już wywołana od ostatniego wywołania next().
Zatem iterator dla List jest najczęstszą implementacją. Iterator przechodzi od początku kolekcji do jej końca: sprawdza, czy występuje następny element i zwraca go, jeśli taki istnieje. Na podstawie tego prostego algorytmu budowany jest cykl for-each. Jego rozszerzeniem jest ListIterator. Przyjrzyjmy się dodatkowym metodom iteratora list Java. Najprawdopodobniej je znasz:
  • void add(E e)— wstawia element Edo ;List
  • boolean hasPrevious()— zwróci true, jeśli Listpodczas 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łanie next()lub previous()element e.
Spójrzmy na mały przykład. Stwórzmy Listplik 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, Seti Queueobsługuj iterator. Funkcja For java Map iteratorjest 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 Entryzawierają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 WordParti 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:
  1. kończący się
  2. przyrostek
  3. konsola
  4. źródło
Projektując własny iterator, możesz określić algorytm iteracji według własnego uznania. Powodzenia na studiach!
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION