Что такое итератор в Java и как его реализовать?

Ответ

Итератор (Iterator) — это паттерн, предоставляющий стандартный способ последовательного доступа к элементам коллекции без раскрытия её внутренней структуры.

Интерфейс java.util.Iterator<E>:

public interface Iterator<E> {
    boolean hasNext();
    E next();
    default void remove() { ... }
    default void forEachRemaining(Consumer<? super E> action) { ... }
}

Реализация собственного итератора:

  1. Класс коллекции должен реализовать интерфейс Iterable<T>, который требует метод iterator().
  2. Метод iterator() возвращает экземпляр класса, реализующего Iterator<T>.

Пример для простой коллекции:

public class CustomCollection<T> implements Iterable<T> {
    private T[] elements;

    @Override
    public Iterator<T> iterator() {
        return new CustomIterator();
    }

    private class CustomIterator implements Iterator<T> {
        private int cursor = 0;

        @Override
        public boolean hasNext() {
            return cursor < elements.length && elements[cursor] != null;
        }

        @Override
        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return elements[cursor++];
        }
    }
}

Использование:

CustomCollection<String> collection = new CustomCollection<>();
for (String item : collection) { // Использует foreach (требует Iterable)
    System.out.println(item);
}

Важно: Итераторы в Java являются fail-fast для большинства стандартных коллекций (например, ArrayList) — при модификации коллекции во время итерации (кроме через remove() самого итератора) выбрасывается ConcurrentModificationException.

Ответ 18+ 🔞

А, ну слушай, вот есть у тебя куча всякого барахла — массив там, список, черт знает что. И тебе надо по нему пройтись, да? Ну, тупо циклом. Но это же скучно, как говно засохшее, и каждый раз одно и то же. А если коллекция у тебя хитрая, блядь, своя собственная? Ты же не будешь всем подряд выворачивать её кишки наружу, показывая, как она там внутри устроена? Вот для этого и придумали итератор, ёпта!

Представь себе, это такой универсальный пульт управления для прохода по коллекции. Как пульт от телика — тебе же похуй, что там внутри, микросхемы какие, ты просто кнопки жмёшь: «следующий», «следующий». Вот и тут так же.

В Яве для этого есть готовый интерфейс java.util.Iterator<E>. Выглядит он просто, хуй с горы:

public interface Iterator<E> {
    boolean hasNext(); // Есть ли ещё что посмотреть, или уже всё, пиздец?
    E next();          // Дай следующую штуку, я жду!
    default void remove() { ... } // Опционально: удали ту штуку, которую только что дал (осторожно, блядь!)
    default void forEachRemaining(Consumer<? super E> action) { ... } // Ну это чтоб не париться, прогнать всё оставшееся
}

Теперь, если ты делаешь свою коллекцию и хочешь, чтобы по ней можно было красиво пройтись циклом for-each (это который for (String s : myCollection)), то тебе надо сделать её Iterable. А это значит — реализовать один-единственный метод iterator(), который вернёт тебе свеженький итератор.

Вот смотри, как это выглядит на практике для какой-нибудь простой коллекции:

public class CustomCollection<T> implements Iterable<T> {
    private T[] elements; // Допустим, тут у нас хранится всё добро

    @Override
    public Iterator<T> iterator() {
        return new CustomIterator(); // Возвращаем свой собственный, самодельный итератор
    }

    // А вот и он, красавец, спрятан внутри как внутренний класс
    private class CustomIterator implements Iterator<T> {
        private int cursor = 0; // Индекс, который помнит, где мы остановились

        @Override
        public boolean hasNext() {
            // Проверяем, не упёрлись ли мы в конец и не наткнулись ли на null
            return cursor < elements.length && elements[cursor] != null;
        }

        @Override
        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException(); // Если больше ничего нет — нахуй так делать, исключение летит
            }
            // Берём элемент и двигаем курсор вперёд
            return elements[cursor++];
        }
    }
}

И теперь использование — просто песня, блядь:

CustomCollection<String> collection = new CustomCollection<>();
// И можно тупо через foreach, потому что класс — Iterable
for (String item : collection) {
    System.out.println(item);
}

Но есть один важный момент, ёбаный в рот! Итераторы в стандартных коллекциях Java (типа ArrayList) — они fail-fast. Это значит, что если ты начал итерироваться по коллекции, а потом где-то сбоку её изменил (добавил или удалил элемент, кроме как через метод remove() самого итератора), то в следующем же шаге тебе вылетит ConcurrentModificationException. Мол, «чувак, бля, коллекция изменилась, я нихуя не понимаю, где я теперь нахожусь, всё, пиздец, я сваливаю». Так что с этим надо аккуратнее, ебушки-воробушки.