Вопрос 15 · Раздел 2

Что такое Iterator pattern?

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 internally, но предоставляет более высокоуровневый 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-Faster баг

  • Удаление элемента в цикле 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