Menu

Цикл for-each в Java: синтаксис, примеры и подводные камни

Цикл for-each в Java (расширенный for) с объяснением: чистая итерация по массивам и коллекциям, когда его использовать и ловушка с изменением, на которой спотыкаются все.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Когда индекс только мешает

Цикл for со счётчиком даёт вам счётчик, условие и шаг обновления. Но в огромном числе случаев позиция элемента вас на самом деле не интересует — вы просто хотите что-то сделать с каждым, по порядку, от начала до конца. Управлять индексом ради этого — лишняя работа, и именно здесь подкрадываются ошибки на единицу (off-by-one).

Цикл for-each (Java называет его расширенным for) полностью убирает счётчик. Вы называете переменную, нацеливаете её на коллекцию, и цикл по очереди выдаёт вам каждый элемент.

Базовый синтаксис

Форма такова: for (Тип элемент : коллекция). Читайте двоеточие как слово «в»:

Нет i, нет scores.length, нет scores[i]. На каждом проходе score и есть следующий элемент. Цикл выполняется один раз на каждый элемент и автоматически останавливается, когда их больше нет — вы не сможете заехать за конец или начать на один элемент раньше.

Перебор списка

Тот же цикл работает с чем угодно перебираемым, включая List, Set и остальные типы коллекций. Тип элемента ставится перед именем переменной:

Обратите внимание: вам не пришлось знать или задумываться, на чём основан langs — на массиве, связном списке или чём-то ещё; for-each работает одинаково со всеми. В этом его настоящая сила: один читаемый синтаксис для каждой коллекции.

var избавляет от написания имени типа

Если тип элемента длинный или очевидный, var позволяет компилятору вывести его, чтобы вы не повторялись:

Без var переменная цикла была бы громоздким Map.Entry<String, Integer>. var сохраняет читаемость, а тип по-прежнему полностью проверяется на этапе компиляции — это не вольный, динамический тип.

Ловушка с изменением

Вот правило, на котором спотыкаются все: нельзя добавлять элементы в коллекцию или удалять их из неё, пока цикл for-each её обходит. Это бросает ConcurrentModificationException:

List<String> items = new ArrayList<>(List.of("a", "b", "c"));

for (String item : items) {
    if (item.equals("b")) {
        items.remove(item);   // бросает ConcurrentModificationException
    }
}

Цикл замечает, что список изменился под ним, и прекращает работу, вместо того чтобы молча пропускать или повторять элементы. Чтобы удалять безопасно, спуститесь к явному Iterator, у которого есть remove(), о котором цикл знает:

Распространённое сокращение — items.removeIf(item -> item.equals("b")), которое делает то же самое в одну строку.

Только чтение, без переприсваивания

Ещё одно тонкое ограничение: присваивание переменной цикла меняет только локальную копию, а не коллекцию. Это удивляет тех, кто пришёл из языков, где переменная цикла — живая ссылка:

Если нужно записать обратно в массив, вам нужен индекс — а это означает классический цикл for со счётчиком: for (int i = 0; i < nums.length; i++) nums[i] = nums[i] * 10;. Для элементов-объектов вы можете изменить объект, на который указывает переменная (например, вызвав сеттер), но не можете заменить его в коллекции.

break и continue по-прежнему работают

for-each — это настоящий цикл, поэтому break и continue ведут себя точно так же, как везде: break выходит из цикла, continue переходит к следующему элементу:

Это выводит keep, затем keep — пропускает "skip" и останавливается на "stop", не дойдя до "never". Так что вы не обязаны посещать каждый элемент; вы просто отказываетесь от индекса в обмен на более чистый код.

Далее: Массивы

Вы уже несколько раз проходили по массивам, не задерживаясь на том, что они такое на самом деле, — контейнеры фиксированного размера с индексами и тем самым полем .length. Следующая страница возвращается к началу и разбирает массивы как следует: как их объявлять, с какими значениями по умолчанию они стартуют и чем их фиксированный размер отличается от растущего ArrayList.

Часто задаваемые вопросы

Что такое цикл for-each в Java?

Цикл for-each (его также называют расширенным for) обходит каждый элемент массива или коллекции без счётчика: for (Тип элемент : коллекция) { ... }. Читается это как «для каждого элемента в коллекции». Он чище, чем for со счётчиком, когда вам нужен только сам элемент и никогда не нужен индекс.

В чём разница между циклом for и циклом for-each в Java?

Классический цикл for использует явный счётчик (for (int i = 0; i < arr.length; i++)), поэтому вы управляете индексом и направлением. Цикл for-each, for (Тип x : arr), не имеет индекса — он посещает каждый элемент по порядку. Используйте for-each для проходов «только чтение» от начала к концу; используйте цикл со счётчиком, когда нужен индекс, нужно пропускать элементы или изменять структуру коллекции.

Почему мой цикл for-each бросает ConcurrentModificationException?

Потому что вы вызвали add() или remove() на коллекции во время её перебора с помощью for-each. Цикл обнаруживает структурное изменение и бросает исключение, чтобы защитить вас от неопределённого поведения. Чтобы безопасно удалять элементы, используйте явный Iterator и его метод remove() или соберите элементы для удаления и удалите их после цикла.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ