Today we’ll talk about what Iterator is in Java and why it is needed.
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.
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
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. |
ArrayList
from LinkedList
and HashSet
from 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 methodremove()
(in the case of read-only collections, for example)IllegalStateException
, if the methodnext()
has not yet been called, or if itremove()
has already been called since the last callnext()
.
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 elementE
into ;List
boolean hasPrevious()
— will returntrue
ifList
there 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 callnext()
orprevious()
with the elemente
.
List
containing 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
, Set
and Queue
support an iterator. For java Map iterator
is 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 Entry
containing 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 WordPart
and 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:
- ending
- suffix
- console
- root
GO TO FULL VERSION