— Привіт, Аміго!
— Привіт, Еллі!
— Сьогодні я хочу розповісти тобі про ітератори.
Ітератори вигадали практично тоді, коли й колекції. Основне завдання колекцій було – зберігати елементи, а основне завдання ітератора – видавати ці елементи по одному.
— А що складного у тому, щоб видати набір елементів?
— По-перше, деякі колекції, наприклад Set не мають встановленого порядку елементів і/або він постійно змінюється.
По-друге, деякі структури даних можуть зберігати об'єкти дуже складно: різними групами, списками тощо. Тобто. завдання віддати послідовно всі елементи буде складним і нетривіальним.
У третіх – колекції мають властивість змінюватися. Вирішив ти вивести на екран весь вміст колекції, а прямо в середині виводу JVM переключилася на іншу нитку, яка половину елементів цієї колекції замінила на іншу. Ось і отримаєш ти замість виводу не зрозумій, що.
— М-так.
— Ось! Саме такі проблеми мав вирішити ітератор. Ітератор – це спеціальний внутрішній об'єкт у колекції, який з одного боку має доступ до всіх її private даних та знає її внутрішню структуру, з іншого – реалізує загальнодоступний інтерфейс Iterator, завдяки чому всі знають, як з ним працювати.
Деякі ітератори мають у собі масив, куди копіюються всі елементи колекції під час створення ітератора. Це гарантує, що подальша зміна колекції не вплине на порядок та кількість елементів.
Думаю, ти вже стикався з тим, що при роботі з for each не можна одночасно «йти по колекції циклом» і видаляти з неї елементи. Це все саме через пристрій ітератора.
У нових колекціях, доданих у бібліотеці concurrency, пристрій ітератора перероблено, тому там такої проблеми немає.
Давай я тобі нагадаю, як влаштований ітератор.
У Java є спеціальний інтерфейс Iterator, ось які у нього методи:
Методи інтерфейсу Iterator<E> | Опис |
---|---|
boolean hasNext() |
Перевіряє, чи є ще елементи |
E next() |
Повертає поточний елемент і перемикається на наступний. |
void remove() |
Видаляє поточний елемент |
Ітератор дає змогу по черзі отримати всі елементи колекції. Логічніше уявити ітератор чимось на зразок InputStream – він має всі дані, але його завдання видавати їх послідовно.
Метод next() повертає наступний (черговий) елемент колекції.
Метод hasNext() використовується, щоб перевіряти, чи є ще елементи.
Ну, а remove() – видаляє поточний елемент.
Питання є?
— А чому методи називаються так дивно? Чому не isEmpty() або getNextElement()?
Хіба так не логічніше?
— Логічне, але такі назви прийшли з мови C++, де ітератори з'явилися раніше.
— Зрозуміло. Продовжимо.
Крім ітератора є ще інтерфейс Iterable – його мають реалізовувати всі колекції, які підтримують ітератор. Він має єдиний метод:
Методи interface Iterable<T> | Опис |
---|---|
Iterator<T>iterator() |
Повертає об'єкт-ітератор |
За допомогою цього методу будь-яку колекцію можна отримати об'єкт ітератор для обходу її елементів. Обійдемо всі елементи дерева в колекції TreeSet:
TreeSet<String> set=new TreeSet<String< span class="dblue_text">>();
Iterator<String> iterator =set.iterator( );
while (iterator.hasNext())
{
String item = iterator.next();
System.out.println(item);
}
Таке використання ітератора не дуже зручне – надто багато зайвого та очевидного коду. Ситуація спростилася, коли в Java з'явився цикл за ітератором - for-each.
Тепер такий код набагато компактніший і читальніший:
Було | Стало |
---|---|
|
|
Це один і той же код! Ітератор використовується і там, і там.
Просто в цикліfor-each його використання приховано. Зверни увагу – у коді справа взагалі немає червоного кольору. Використання ітератора повністю приховано.
Цикл for-each можна використовувати для будь-яких об'єктів, які підтримують ітератор. Тобто. ти можеш написати свій клас, додати йому метод iterator() і зможеш використовувати його об'єкти в правій частині конструкції for-each.
— Ого! Я, звичайно, не прагну писати власні колекції та ітератори, але пропозиція все одно приваблива. Візьму на олівець.
— Крім того, є ще один популярний різновид ітераторів, для якого навіть придумали свій інтерфейс. Йдеться про ітератора для списків –ListIterator.
Списки, незалежно від реалізації, володіють порядком елементів, що дозволяє працювати з ними через ітератор трохи зручніше.
Ось які методи є у інтерфейсу ListIterator<E>:
Метод | Опис |
---|---|
boolean hasNext() |
Перевіряє, чи є ще елементи попереду. |
E next() |
Повертає наступний елемент. |
int nextIndex() |
Повертає індекс наступного елемента |
void set(E e) |
Змінює значення поточного елемента |
boolean hasPrevious() |
Перевіряє, чи є елементи позаду. |
E previous() |
Повертає попередній елемент |
int previousIndex() |
Повертає індекс попереднього елемента |
void remove() |
Видаляє поточний елемент |
void add(E e) |
Додає елемент до списку. |
Тобто. Тут ми можемо ходити уперед, а й назад. І ще пара фіч по дрібниці.
— Що ж, цікава річ. А де його використовують?
— Наприклад, ти хочеш рухатися туди-назад за зв'язковим списком. При цьому операція get буде досить повільною, а операція next() дуже швидкою.
— Хм. Переконала. Маю на увазі.
Дякую, Еллі!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ