Питання 15 · Розділ 2

Що таке патерн Iterator?

4. Fail-Safe колекції для багатопотоковості 5. Не модифікуйте колекцію під час ітерації

Мовні версії: English Russian Ukrainian

🟢 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

  1. iterator.remove() для видалення під час перебору
  2. removeIf() для фільтрації (Java 8+)
  3. Stream для складних операцій
  4. Fail-Safe колекції для багатопотоковості
  5. Не модифікуйте колекцію під час ітерації

Резюме для 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