Сьогодні поговоримо про те, що таке Iterator у Java і навіщо він потрібний.
Як ти вже, мабуть, знаєш, Java має чудовий інтерфейс Collection, що реалізує інтерфейс Iterator. Відразу обмовлюся, не слід плутати інтерфейс iterator з патерном iterator у Java! І щоб внести ясність, спочатку розберемося саме з інтерфейсом.
Уявимо на секунду, що iterator у Java відсутня. У такому разі всім і кожному доведеться пірнути в самі глибини колекцій і по-справжньому розібратися, чим відрізняється
Дослівно "Iterator" можна перекласти як "перебірник" . Тобто, це якась сутність, здатна перебрати всі елементи в колекції. При цьому вона дозволяє це зробити без вникання у внутрішню структуру та влаштування колекцій. |
ArrayList
від LinkedList
і HashSet
від TreeSet
.
Методи, які має імплементувати Iterator
boolean hasNext()
— якщо в об'єкті, що ітерується (поки що це Collection) залишабося ще значення — метод поверне true
, якщо значення скінчабося false
. E next()
- Повертає наступний елемент колекції (об'єкта). Якщо елементів більше немає (не було перевірки hasNext()
, а ми викликали next()
, досягнувши кінця колекції), метод кине NoSuchElementException
. void remove()
- Видалить елемент, який був востаннє отриманий методом next()
. Метод може залишити:
UnsupportedOperationException
якщо цей ітератор не підтримує методremove()
(у випадку з read-only колекціями, наприклад)IllegalStateException
якщо методnext()
ще не був викликаний, або якщоremove()
вже був викликаний після останнього викликуnext()
.
for-each
. Його розширення - ListIterator. Розглянемо додаткові способи java list iterator. Ти їх, швидше за все, знаєш:
void add(E e)
- Вставляє елементE
в ;List
boolean hasPrevious()
- Повернеtrue
, якщо при зворотному переборіList
є елементи;int nextIndex()
- Поверне індекс наступного елемента;E previous()
- Поверне попередній елемент листа;int previousIndex()
- Поверне індекс попереднього елемента;void set(E e)
— замінить елемент, повернутий останнім викликомnext()
абоprevious()
елементe
.
List
, що містить у собі рядки привітання учнів:
List<String> list = new ArrayList<>();
list.add("Вітання");
list.add("Обучающимся");
list.add("На");
list.add("JavaRush");
Тепер отримаємо для нього ітератор і виведемо в консоль усі рядки:
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
Зараз буде "вузьке місце": Java Collections як ти, ймовірно, знаєш (а якщо не знаєш, розберися), розширюють інтерфейс, Iterable
але це не означає, що тільки List
, Set
і Queue
підтримують ітератор. Також підтримується java Map iterator
, але його необхідно викликати для Map.entrySet()
:
Map<String, Integer> map = new HashMap<>();
Iterator mapIterator = map.entrySet().iterator();
Тоді метод next()
повертатиме об'єкт Entry
, що містить у собі пару «ключ»-«значення». Далі все аналогічно з List
:
while (mapIterator.hasNext()) {
Map.Entry<String, Integer> entry = mapIterator.next();
System.out.println("Key: " + entry.getKey());
System.out.println("Value: " + entry.getValue());
}
Ти думаєш: «Стоп. Ми говоримо про інтерфейс, а в заголовку статті написано "Паттерн". Тобто патерн iterator – це інтерфейс Iterator? Чи інтерфейс – це патерн?» Якщо це слово зустрічається вперше, даю довідку: патерн — це шаблон проектування, така собі поведінка, якої має дотримуватися клас або безліч взаємопов'язаних класів. Ітератор java може бути реалізований для будь-якого об'єкта, внутрішня структура якого передбачає перебір, при цьому можна змінити сигнатуру обговорюваних методів. Головне під час реалізації патерну – логіка, якої має дотримуватися клас. Інтерфейс ітератор – приватна реалізація однойменного патерну, що застосовується як до готових структур (List, Set, Queue, Map
), і до інших, на розсуд програміста. Розширюючи інтерфейс Iterator, ти реалізуєш патерн, але для реалізації патерна не обов'язково розширювати інтерфейс. Проста аналогія: усі риби плавають, але не все, що плаває – риби. Як приклад я вирішив взяти… слово. А конкретніше — іменник. Воно складається з частин: приставки, кореня, суфікса та закінчення. Для частин слова створимо інтерфейс WordPart
і класи, що розширюють його 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;
}
}
Тоді клас 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;
}
Окей, у нас є чотири перевантажені конструктори (для простоти, припустимо, що суфікс у нас може бути лише один). Іменник не може складатися з однієї приставки, тому для конструктора з одним параметром будемо встановлювати корінь. Тепер напишемо реалізацію патерну ітератор: WordIterator, що перевизначає 2 методи: hasNext()
і 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--;
}
}
}
Залишилося призначити ітератор класу Word
:
public class Word implements Iterable<Word.WordPart> {
…
@Override
public Iterator<WordPart>iterator() {
return new WordIterator(this);
}
…
}
Тепер проведемо морфемний розбір слова «перебіжка»:
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());
}
}
}
Зверніть увагу, у своїй реалізації патерну iterator я вибрав наступний порядок виведення:
- закінчення
- суфікс
- префікс
- корінь
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ