Що таке патерн Iterator?
4. Fail-Safe колекції для багатопотоковості 5. Не модифікуйте колекцію під час ітерації
🟢 Junior Level
Iterator — патерн для перебору елементів колекції, не знаючи її внутрішнього устрою.
Проста аналогія: Пульт від телевізора. Ви перемикаєте канали кнопкою “Next”, не знаючи, як телевізор всередині перемикає канали.
Приклад:
// Iterator у Java — це те, що використовує forEach
List<String> list = List.of("A", "B", "C");
// Явний Iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
}
// Те саме через forEach (використовує Iterator всередині)
list.forEach(System.out::println);
Навіщо потрібен:
- Єдиний спосіб перебору для будь-яких колекцій
- Не потрібно знати внутрішній устрій
- Можна видаляти елементи під час перебору
🟡 Middle Level
Iterator vs ListIterator
List<String> list = new ArrayList<>(List.of("A", "B", "C"));
// List.of() повертає immutable список, add() викине UnsupportedOperationException
// Iterator — тільки вперед
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
it.remove(); // Можна видаляти
}
// ListIterator — вперед і назад
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
lit.next();
lit.add("new"); // Можна додавати!
}
while (lit.hasPrevious()) {
lit.previous(); // Можна назад!
}
Власний Iterator
class Tree<T> {
private Node<T> root;
public Iterator<T> iterator() {
return new TreeIterator();
}
private class TreeIterator implements Iterator<T> {
private Queue<Node<T>> queue = new LinkedList<>();
TreeIterator() {
if (root != null) queue.add(root);
}
public boolean hasNext() {
return !queue.isEmpty();
}
public T next() {
Node<T> node = queue.poll();
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
return node.value;
}
}
}
🔴 Senior Level
Fail-Fast vs Fail-Safe
// Fail-Fast (ArrayList, HashMap)
List<String> list = new ArrayList<>(List.of("A", "B", "C"));
Iterator<String> it = list.iterator();
it.next();
list.add("D"); // Модифікація колекції
it.next(); // → ConcurrentModificationException!
// Fail-Safe (CopyOnWriteArrayList, ConcurrentHashMap)
List<String> safeList = new CopyOnWriteArrayList<>(List.of("A", "B", "C"));
Iterator<String> safeIt = safeList.iterator();
safeIt.next();
safeList.add("D"); // Модифікація
safeIt.next(); // Не кидає exception, але ітератор бачить тільки елементи на момент створення (snapshot). Новий елемент "D" НЕ буде видно у поточній ітерації.
Stream API як Iterator
// Stream API використовує Spliterator internal, але надає більш високорівневий API
// (lazy evaluation, parallel streams, functional composition).
list.stream()
.filter(s -> s.startsWith("A"))
.map(String::toUpperCase)
.forEach(System.out::println);
// Stream підтримує lazy evaluation
// Iterator — ні
Production Experience
Реальний сценарій: Fail-Fast баг
- Видалення елемента у циклі forEach → ConcurrentModificationException
- Рішення: iterator.remove() або removeIf()
- Урок: не можна модифікувати колекцію під час ітерації
Best Practices
- iterator.remove() для видалення під час перебору
- removeIf() для фільтрації (Java 8+)
- Stream для складних операцій
- Fail-Safe колекції для багатопотоковості
- Не модифікуйте колекцію під час ітерації
Резюме для Senior
- Iterator = універсальний спосіб перебору
- Fail-Fast = exception при модифікації
- Fail-Safe = ітератор по копії
- Stream API = використовує Spliterator, надає lazy evaluation і functional composition
- removeIf() пріоритетніший за ручне видалення
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
- Iterator — універсальний спосіб перебору, що приховує внутрішній устрій колекції
- Fail-Fast (ArrayList, HashMap) — ConcurrentModificationException при модифікації під час ітерації
- Fail-Safe (CopyOnWriteArrayList, ConcurrentHashMap) — ітератор по snapshot, не кидає exception
- Stream API використовує Spliterator — lazy evaluation, parallel streams, functional composition
- Для видалення під час ітерації: iterator.remove() або removeIf() (Java 8+)
- ListIterator розширює Iterator: рух назад, додавання елементів
- Не можна модифікувати колекцію під час ітерації (крім iterator.remove() і Fail-Safe колекцій)
Часті уточнювальні запитання:
- Як видалити елемент під час перебору? — iterator.remove() або list.removeIf(predicate)
- Чим Fail-Fast відрізняється від Fail-Safe? — Fail-Fast кидає exception, Fail-Safe працює по snapshot
- Чому forEach не дає видаляти елементи? — forEach використовує Iterator всередині, модифікація → ConcurrentModificationException
- Що таке Spliterator? — Parallel-capable Iterator для Stream API, підтримує partitioning
Червоні прапорці (НЕ говорити):
- “Я модифікую колекцію у циклі forEach” — ConcurrentModificationException гарантований
- “Fail-Safe означає що дані завжди актуальні” — ітератор бачить snapshot, нові елементи НЕ видно
- “Iterator застарів з приходом Stream API” — Iterator використовується під капотом Stream і forEach
- “removeIf() — це те саме що iterator.remove()” — removeIf() приймає Predicate, більш високорівневий
Пов’язані теми:
- [[11. Як Observer реалізований в Java]] — перебір слухачів
- [[2. Які категорії патернів існують]] — Behavioral патерни
- [[10. Коли використовувати Strategy]] — Stream API як альтернатива
- [[16. Які антипатерни ви знаєте]] — God Object з колекціями
- [[1. Що таке патерни проектування]] — Iterator у Modern Java