Одна задача, несколько инструментов
Вы собрали какие-то данные - в ArrayList, HashSet, HashMap - и теперь хотите обойти каждый элемент. Java предлагает несколько способов сделать это, и выбор зависит от того, нужен ли вам индекс, нужно ли удалять элементы посреди цикла и предпочитаете ли вы синтаксис в виде метода или в виде цикла.
Хорошая новость: каждая Collection (список, множество, очередь) поддерживает один и тот же расширенный цикл for, так что, выучив его однажды, вы сможете перебирать их все одинаково.
Читайте двоеточие как «в»: «для каждого lang в langs». Вы никогда не трогаете индекс, поэтому здесь нечему пойти не так.
Цикл for-each
Расширенный for - выбор по умолчанию для простого «сделать что-то с каждым элементом». Он читается ясно и работает одинаково для всех типов коллекций - вот HashSet, у которого вообще нет индексов:
Что стоит помнить о HashSet: у него нет определённого порядка, поэтому элементы могут вывестись в любой последовательности. Тем не менее for-each посещает каждый из них ровно один раз.
Когда нужен индекс
for-each даёт вам элемент, но не его позицию. Если индекс вам действительно нужен - чтобы нумеровать строки или смотреть на соседние элементы - используйте цикл со счётчиком, size() и get(i). Это работает с List, который позиционен; у множеств и отображений нет индекса, поэтому этот стиль к ним неприменим.
Не прибегайте к этому просто по привычке. Если вы не используете i ни для чего, кроме get(i), вариант с for-each короче, и в нём труднее ошибиться.
Перебор Map
Map не является Collection, поэтому пройтись по ней напрямую через for-each нельзя. Вместо этого перебирают одно из трёх её представлений. Самое распространённое - entrySet(), которое выдаёт каждую пару ключ-значение вместе:
Если вам нужны только ключи, перебирайте ages.keySet(); если только значения - ages.values(). Предпочитайте entrySet(), когда нужны оба - перебор ключей с последующим вызовом ages.get(key) внутри без всякой причины выполняет повторный поиск на каждой итерации.
Iterator
for-each на самом деле является синтаксическим сахаром над Iterator - объектом, который обходит коллекцию по одному элементу через hasNext() и next(). Этот цикл вы редко пишете вручную, за одним важным исключением: это безопасный способ удалять элементы во время итерации.
it.remove() удаляет элемент, который next() вернул последним, и итератор остаётся корректным. Это единственный санкционированный способ изменять коллекцию во время ручного цикла.
Ловушка: ConcurrentModificationException
Если вы вызовете add или remove на самой коллекции внутри цикла for-each, вы получите ConcurrentModificationException - итератор замечает, что коллекция изменилась у него под ногами, и отказывается продолжать. Это одна из самых частых ошибок новичков.
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4));
for (int n : nums) {
if (n % 2 == 0) {
nums.remove(Integer.valueOf(n)); // throws ConcurrentModificationException
}
}
Решение почти всегда - removeIf, который выражает намерение в одну строку и сам берёт итерацию на себя:
removeIf работает с любой Collection, поэтому тот же вызов очистит и HashSet.
Метод forEach
У каждой коллекции есть также метод forEach, который принимает лямбду и выполняет её для каждого элемента. Это более функциональная, выразительная альтернатива циклу - удобная для коротких однострочников:
Обратите внимание, что Map.forEach принимает лямбду с двумя аргументами (key, value) напрямую - entrySet() не нужен. Используйте forEach для быстрых побочных эффектов; возвращайтесь к обычному циклу for, когда тело разрастается или нужно break, чтобы выйти раньше, чего лямбда сделать не может.
Далее: Методы
Теперь вы упаковали данные в коллекции и обошли их всеми способами, которые предлагает Java. Следующий шаг - упаковать поведение: написать собственные методы, чтобы дать имя блоку логики, передавать ему входные данные и переиспользовать его - об этом следующая страница.
Часто задаваемые вопросы
Как перебрать список в Java?
Самый чистый способ - расширенный цикл for (for-each): for (String s : list) { ... }. Он работает с любой Collection - ArrayList, HashSet и так далее. Цикл по индексу с get(i) используйте только тогда, когда вам действительно нужна позиция, а Iterator - когда нужно удалять элементы во время цикла.
Как пройтись по HashMap в Java?
Map не является Collection, поэтому перебирают одно из её представлений. Обычный выбор - for (Map.Entry<K, V> e : map.entrySet()), который даёт и ключ (e.getKey()), и значение (e.getValue()) за один проход. Также можно перебрать map.keySet() для ключей или map.values() для значений.
Почему при переборе возникает ConcurrentModificationException?
Вы вызвали add или remove на коллекции, пока цикл for-each её перебирал. Цикл for-each под капотом использует Iterator, и тот обнаруживает, что коллекция изменилась структурно. Исправьте это, удаляя через собственный метод remove() у Iterator, или вызвав removeIf(...) вместо цикла.