1. Интерфейс Iterable
В Java почти все коллекции (кроме Map) реализуют интерфейс Iterable. Это значит, что по ним можно проходить последовательно — элемент за элементом, не вдаваясь в детали внутренней структуры. Для программиста это выглядит как «у коллекции есть встроенный способ пройтись по всем элементам».
Интерфейс Iterable определяет ровно один метод:
Iterator<E> iterator();
Метод iterator() возвращает объект типа Iterator — «помощника», который знает, как пройти коллекцию шаг за шагом. Благодаря этому работает привычный цикл for-each:
for (ElementType e : collection) {
// ...
}
— за кулисами как раз прячется тот самый Iterator. Дополнительный плюс: с его помощью можно безопасно удалять элементы во время обхода методом remove(). Если пытаться делать это обычным перебором, легко поймать ConcurrentModificationException.
2. Интерфейс Iterator
Iterator — это «курьер», который умеет идти по вашей коллекции, не пропуская элементы и соблюдая её порядок обхода.
| Метод | Описание |
|---|---|
|
Есть ли ещё элементы для перебора? |
|
Вернуть следующий элемент и перейти к нему |
|
Удалить текущий элемент безопасно, без сбоев |
Пример перебора коллекции через 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()
}
}
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ