JavaRush /Java Blog /Random EN /Iterator Pattern

Iterator Pattern

Published in the Random EN group
Today we’ll talk about what Iterator is in Java and why it is needed.
Iterator pattern - 1
As you probably already know, Java has a wonderful Collection interface that implements the Iterator interface. Let me make a reservation right away: the iterator interface should not be confused with the iterator pattern in Java! And to clarify, let’s first look at the interface.
Literally, “Iterator” can be translated as “brute force . ” That is, it is a certain entity that can iterate through all the elements in the collection. Moreover, it allows you to do this without delving into the internal structure and arrangement of collections.
Let's imagine for a second that there is no iterator in Java. In this case, each and every one will have to dive into the very depths of the collections and truly understand what is different ArrayListfrom LinkedListand HashSetfrom TreeSet.

Methods that Iterator must implement

boolean hasNext()— if there are still values ​​left in the iterable object (currently a Collection), the method will return true, if there are no more values false. E next()— returns the next element of the collection (object). If there are no more elements (there was no check hasNext(), and we called next()when we reached the end of the collection), the method will throw NoSuchElementException. void remove()- will remove the element that was last obtained by the next(). The method can throw:
  • UnsupportedOperationException, if this iterator does not support the method remove()(in the case of read-only collections, for example)
  • IllegalStateException, if the method next()has not yet been called, or if it remove()has already been called since the last call next().
So, iterator for List is the most common implementation. The iterator goes from the beginning of the collection to its end: it looks to see if the next element is present and returns it if there is one. A cycle is built on the basis of this simple algorithm for-each. Its extension is ListIterator. Let's look at additional java list iterator methods. You most likely know them:
  • void add(E e)— inserts an element Einto ;List
  • boolean hasPrevious()— will return trueif Listthere are elements during the reverse search;
  • int nextIndex()— will return the index of the next element;
  • E previous()— will return the previous sheet element;
  • int previousIndex()— will return the index of the previous element;
  • void set(E e)- will replace the element returned by the last call next()or previous()with the element e.
Let's look at a small example. Let’s create a Listcontaining the lines of greetings to students:
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("Обучающимся");
list.add("На");
list.add("JavaRush");
Now we’ll get an iterator for it and print all the contained lines to the console:
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}
Now there will be a “bottleneck”: Java Collections, as you probably know (and if you don’t know, figure it out), extend the interface Iterable, but this does not mean that only List, Setand Queuesupport an iterator. For java Map iteratoris also supported, but must be called for Map.entrySet():
Map<String, Integer> map = new HashMap<>();
Iterator mapIterator = map.entrySet().iterator();
Then the method next() will return an object Entrycontaining a “key”-“value” pair. Then everything is the same with List:
while (mapIterator.hasNext()) {
    Map.Entry<String, Integer> entry = mapIterator.next();
    System.out.println("Key: " + entry.getKey());
    System.out.println("Value: " + entry.getValue());
}
You think: “Stop. We are talking about the interface, and the title of the article says “Pattern”. That is, the iterator pattern is the Iterator interface? Or is the interface a pattern? If this word appears for the first time, I give you a reference: a pattern is a design pattern, a certain behavior that a class or many interconnected classes must adhere to. An iterator in java can be implemented for any object whose internal structure involves iterating, and you can change the signature of the methods being discussed. The main thing when implementing a pattern is the logic that the class must adhere to. The iterator interface is a private implementation of the pattern of the same name, applied both to ready-made structures ( List, Set, Queue, Map), and to others, at the discretion of the programmer. By extending the Iterator interface, you implement a pattern, but you do not need to extend the interface to implement the pattern. A simple analogy: all fish swim, but not everything that swims is a fish. As an example, I decided to take... the word. More specifically, a noun. It consists of parts: prefix, root, suffix and ending. For parts of a word, we will create an interface WordPartand classes that extend it: 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;
    }
}
Then the class Word(word) will contain parts, and in addition to them we will add an integer reflecting the number of parts in the word:
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;
    }
Okay, we have four overloaded constructors (for simplicity, let's assume we can only have one suffix). A noun cannot consist of one prefix, so for a constructor with one parameter we will set the root. Now let’s write an implementation of the iterator pattern: WordIterator, overriding 2 methods: hasNext()and 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--;
        }
    }
}
All that remains is to assign the iterator to the class Word:
public class Word implements Iterable<Word.WordPart> {@Override
	public Iterator<WordPart>iterator() {
    		return new WordIterator(this);
	}}
Now let’s conduct a morphemic analysis of the word “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());
        }
    }
}
Please note that in my implementation of the iterator pattern, I chose the following output order:
  1. ending
  2. suffix
  3. console
  4. root
When designing your own iterator, you can specify the iteration algorithm as you wish. Good luck in your studies!
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION