JavaRush /Курсы /JAVA 25 SELF /Iterable и Iterator: перебор коллекций

Iterable и Iterator: перебор коллекций

JAVA 25 SELF
27 уровень , 1 лекция
Открыта

1. Интерфейс Iterable

В Java почти все коллекции (кроме Map) реализуют интерфейс Iterable. Это значит, что по ним можно проходить последовательно — элемент за элементом, не вдаваясь в детали внутренней структуры. Для программиста это выглядит как «у коллекции есть встроенный способ пройтись по всем элементам».

Интерфейс Iterable определяет ровно один метод:

Iterator<E> iterator();

Метод iterator() возвращает объект типа Iterator — «помощника», который знает, как пройти коллекцию шаг за шагом. Благодаря этому работает привычный цикл for-each:

for (ElementType e : collection) {
    // ...
}

— за кулисами как раз прячется тот самый Iterator. Дополнительный плюс: с его помощью можно безопасно удалять элементы во время обхода методом remove(). Если пытаться делать это обычным перебором, легко поймать ConcurrentModificationException.

2. Интерфейс Iterator

Iterator — это «курьер», который умеет идти по вашей коллекции, не пропуская элементы и соблюдая её порядок обхода.

Метод Описание
boolean hasNext()
Есть ли ещё элементы для перебора?
E next()
Вернуть следующий элемент и перейти к нему
void remove()
Удалить текущий элемент безопасно, без сбоев

Пример перебора коллекции через Iterator

import java.util.*;

public class IteratorDemo {
    public static void main(String[] args) {
        List<String> tasks = new ArrayList<>();
        tasks.add("Погладить кота");
        tasks.add("Сделать домашку");
        tasks.add("Посмотреть сериал");

        Iterator<String> it = tasks.iterator();
        while (it.hasNext()) {
            String task = it.next();
            System.out.println("Задача: " + task);
        }
    }
}

Что здесь происходит?

  • Получаем итератор через tasks.iterator().
  • Пока есть следующий элемент (hasNext() возвращает true), берём его через next() и выводим.
  • Итератор сам следит за порядком обхода — вам не нужно знать, как коллекция хранит элементы внутри.

3. Почему нужен Iterator, если есть циклы?

С помощью Iterator можно перебирать любую коллекцию, даже без индексов (например, Set). Это универсальный способ, который не зависит от конкретного типа коллекции.

Безопасное удаление элементов

Частая задача: пройти по коллекции и удалить некоторые элементы. Если делать это for-each, можно получить ошибку:

for (String task : tasks) {
    if (task.contains("кот")) {
        tasks.remove(task); // БУМ! ConcurrentModificationException
    }
}

Почему так происходит? Коллекция не ждёт, что её структуру будут менять напрямую во время обхода, инициированного итератором.

Правильный способ:

Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
    String task = it.next();
    if (task.contains("кот")) {
        it.remove(); // Всё пройдёт гладко!
    }
}

Почему нельзя просто использовать индексы?

Потому что не у всех коллекций есть индексы. Например, у HashSet или TreeSet нет понятия «пятого элемента». Iterator работает всегда — в этом его сила.

4. Подробности о for-each

Улучшенный цикл for (for-each) появился ещё в Java 5. По сути это синтаксический сахар, позволяющий перебирать элементы максимально просто:

for (String task : tasks) {
    System.out.println("Задача: " + task);
}

Под капотом компилятор вызывает iterator(), проверяет элементы через hasNext() и достаёт их с помощью next(). Читается буквально как фраза на человеческом языке: «для каждой задачи из списка».

Когда for-each не подойдёт?

  • Нужно удалять элементы во время обхода (for-each не даёт вызвать remove() напрямую).
  • Нужен доступ к индексу, чтобы, например, заменить элемент по позиции.
  • Вы работаете с Map — у неё пары «ключ-значение», для перебора нужна своя логика.

5. Перебор Map: хитрости и нюансы

Интерфейс Map не реализует Iterable напрямую, так как это набор пар «ключ-значение». Тем не менее, Map предоставляет удобные представления для перебора.

Перебор по ключам

Map<String, String> users = new HashMap<>();
users.put("vasya", "vasya@example.com");
users.put("petya", "petya@gmail.com");

for (String login : users.keySet()) {
    System.out.println("Логин: " + login);
}

Перебор по значениям

for (String email : users.values()) {
    System.out.println("Email: " + email);
}

Перебор по парам (ключ-значение)

Самый универсальный способ — перебор по entrySet():

for (Map.Entry<String, String> entry : users.entrySet()) {
    System.out.println("Логин: " + entry.getKey() + ", Email: " + entry.getValue());
}

Интересный факт: Entry — это внутренний интерфейс Map с методами getKey() и getValue(). Так вы сразу получаете обе части пары.

Перебор через Iterator

Iterator<Map.Entry<String, String>> it = users.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<String, String> entry = it.next();
    // Можно даже безопасно удалить элемент:
    if (entry.getKey().startsWith("v")) {
        it.remove();
    }
}

6. Примеры из жизни: как перебор коллекций помогает в приложении

Пример: выводим все задачи пользователя

List<String> tasks = new ArrayList<>();
tasks.add("Сделать домашку");
tasks.add("Погладить кота");
tasks.add("Посмотреть сериал");

System.out.println("Ваши задачи на сегодня:");
for (String task : tasks) {
    System.out.println("- " + task);
}

Теперь удалим все задачи, содержащие слово "кот":

Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
    String task = it.next();
    if (task.contains("кот")) {
        it.remove();
    }
}
System.out.println("Остались задачи:");
for (String task : tasks) {
    System.out.println("- " + task);
}

Пример: перебор уникальных логинов через Set

Set<String> logins = new HashSet<>();
logins.add("vasya");
logins.add("petya");
logins.add("masha");

for (String login : logins) {
    System.out.println("Пользователь: " + login);
}

Обратите внимание: порядок вывода у Set может быть любым!

Пример: перебор Map для отображения пользователей

Map<String, String> users = new HashMap<>();
users.put("vasya", "vasya@example.com");
users.put("petya", "petya@gmail.com");

for (Map.Entry<String, String> entry : users.entrySet()) {
    System.out.println("Логин: " + entry.getKey() + ", Email: " + entry.getValue());
}

7. Iterator.remove(): безопасное удаление элементов

Одна из самых частых ошибок новичков — попытка удалить элементы коллекции во время перебора через for-each. Итератор решает эту проблему с помощью remove().

Как это работает?

  • При вызове it.remove() удаляется текущий элемент — тот, который вернул последний next().
  • Это безопасно: коллекция не выбрасывает ConcurrentModificationException.

Пример:

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
    int n = it.next();
    if (n % 2 == 0) {
        it.remove(); // Удаляем все чётные числа
    }
}
System.out.println(numbers); // [1, 3, 5]

Схема перебора коллекции

+---------+     +---------+     +---------+
| Element | --> | Element | --> | Element | ...
+---------+     +---------+     +---------+
     ^               ^
     |               |
   next()         next()

Итератор «шагает» по элементам, пока hasNext() не вернёт false.

9. Типичные ошибки при работе с Iterator и перебором коллекций

Ошибка №1: модификация коллекции во время перебора через for-each.
Попытка удалить элемент прямо внутри for-each приводит к ConcurrentModificationException:

for (String task : tasks) {
    if (task.contains("кот")) {
        tasks.remove(task); // БУМ! ConcurrentModificationException
    }
}

Используйте Iterator и его remove().

Ошибка №2: вызов remove() до next().
Сначала нужно получить текущий элемент через next(), иначе итератор не понимает, что удалять.

Iterator<String> it = tasks.iterator();
it.remove(); // Ошибка! Сначала нужен next()

Ошибка №3: попытка перебирать Map напрямую в for-each.
Map не реализует Iterable напрямую — используйте keySet(), values() или entrySet().

Map<String, String> users = new HashMap<>();
// for (String entry : users) { ... } // Ошибка: так нельзя
for (Map.Entry<String, String> e : users.entrySet()) {
    // правильно
}

Ошибка №4: изменение коллекции вне итератора при обходе итератором.
Во время обхода удаляйте элементы только через it.remove(), а не через методы коллекции.

Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
    String task = it.next();
    if (task.contains("кот")) {
        tasks.remove(task); // Ошибка! Нужно it.remove()
    }
}
1
Задача
JAVA 25 SELF, 27 уровень, 1 лекция
Недоступна
Словарь инопланетных языков 👽
Словарь инопланетных языков 👽
1
Задача
JAVA 25 SELF, 27 уровень, 1 лекция
Недоступна
Управление списком технологий для проекта 🛠️
Управление списком технологий для проекта 🛠️
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ