Как использовать циклы в языке Java. Полное руководство
Всё — от простейших конструкций до тонкостей и самых неочевидных моментов — в одной статье.
Цикл — это конструкция, которая позволяет многократно выполнять один и тот же участок кода. Например, вы хотите написать в консоль слово «Привет» 10 раз. Это можно сделать таким образом:
Вроде бы и не очень сложно, но код постоянно дублируется. А если нам нужно повторить это 100 раз — не добавлять же в программу 100 одинаковых строк? В таких случаях на помощь приходят циклы.
Вот как можно записать этот же код с помощью одного из циклов (далее мы рассмотрим его подробнее) — получилось всего три строчки вместо десяти:
А вот вариант для стократного вывода в консоль сообщения «Привет»:
Количество строк не увеличилось, мы просто заменили число 10 на число 100. Ровно так же будет и в случае с тысячей строк, и с миллионом, и с любым другим числом повторений.
В языке Java существует четыре вида циклов:
- циклы for;
- циклы while;
- циклы do…while;
- и циклы foreach.
Начнём рассматривать их по порядку.
Цикл for
В самом начале мы уже привели пример, в котором использовался именно цикл for — для вывода повторяющихся строк. Рассмотрим его подробнее.
Синтаксис цикла for имеет такой вид:
Здесь итерация — одно выполнение тела цикла. Смысл параметров, используемых в записи цикла:
- <начальное действие> — в этом месте обычно объявляется счётчик цикла, но может быть произведено любое иное действие. Данная часть выполнится только единожды, перед началом цикла.
- <условие выполнения цикла> — в этой части мы указываем условие для цикла. Если условие возвращает true, то цикл выполняет указанные в его цикле действия, иначе — прекращает работу.
- <действие после итерации> — действие, которое будет выполнено после каждой итерации, если она не была прервана. Если в параметре <начальное действие> счётчик цикла обычно объявляется, то здесь он, как правило, увеличивается. Но может быть совершено и любое другое действие.
Эти три части цикла указываются в круглых скобках цикла и обязательно разделяются знаком точки с запятой. За ними следует:
- <тело цикла> — код, который будет выполняться при каждой итерации.
Последовательность выполнения цикла (пошаговый алгоритм работы):
Шаг 1. Выполняем <начальное действие>, переходим к шагу 2.
Шаг 2. Проверяем <условие выполнения цикла>. Если условие вернуло true, переходим к шагу 3, иначе — к шагу 5.
Шаг 3. Выполняем итерацию цикла (команды, которые записаны в строках <тело цикла>). Если выполнение итерации не было прервано, то переходим к шагу 4, иначе — к шагу 5.
Шаг 4. Выполняем <действие после итерации>, затем переходим к шагу 2.
Шаг 5. Выходим из цикла, продолжаем работу программы.
Для нашего примера (вывод повторяющихся строк) можно нарисовать такую блок-схему:
Разберём детально, как это работает. Наш код:
Цикл объявляет переменную i со значением 0, задаёт условие «пока i меньше 10», действие после итерации «увеличивать i на 1» и команду «выводить в консоль слово „Привет“» для каждой итерации.
В соответствии с синтаксисом <начальное действие> здесь — это объявление переменной i типа int и присвоение ей значения 0 (int i = 0), <условие выполнения цикла> — условие i < 10. <Действием после итерации> в примере является инкремент переменной i (i++). В процессе итерации <тела цикла> происходить вывод в консоль сообщения (System.out.println («Привет»).
Попробуем слегка изменить наш код, чтобы вывести в консоль не приветствие, а значение счётчика i (оно должно увеличиваться с 0 до 9):
Как видим, последним числом выводится 9, потому что в условии цикла указано, что он выполняется, пока счётчик i < 10. После завершения последней (десятой) итерации, когда i = 9, отработает инкремент и значение счётчика станет равно 10.
Если нужно, мы можем сделать счётчик от 0 до 10. Для этого достаточно изменить условие c i < 10 на i <= 10. Но в этом случае на печать будет выведено не 10, а 11 строк:
Важные замечания насчёт цикла for:
1. Переменную, которую мы объявляем в параметрах цикла, нельзя использовать за его пределами, так как область видимости i ограничена телом цикла. Такая запись, например, не будет скомпилирована:
Чтобы получить доступ к значению переменной i за пределами цикла, её нужно объявить за пределами цикла. Например:
2. В цикле for ни одно действие не является обязательным, главное — указать два знака точки с запятой. Это значит, что в цикле мы можем опустить:
- условие (важно проследить, чтобы цикл обязательно был прерван, иначе он станет бесконечным);
- параметр, задающий действие после итерации;
- действие перед циклом;
- два любых параметра либо абсолютно все.
Пример:
Следующий вариант эквивалентен, так как инкремент i++ происходит прямо в конце итерации:
Ещё вариант, тоже аналогичный первому, так как мы поставили условие прерывания цикла break (о прерываниях поговорим позже):
3. Первым параметром цикла <начальное действие> может быть всё что угодно. Например, вызов метода:
Мы можем даже увеличить число, поместив прямо здесь инкремент i++ (в этом случае начальное значение i в цикле будет равно 1):
Аналогично можно делать всё, что пожелаем, и в <действие после итерации>. Например, тоже вызвать какой-то метод:
Важно понимать, что между собой параметры for никак не связаны. Поэтому в качестве условия цикла может выступать любое выражение. Пример:
4. Первое действие цикла for может состоять из любого числа команд либо объявления любого количества переменных одного типа. Например, здесь мы вызываем метод, увеличиваем any на 2 и увеличиваем i на 1:
А тут первым действием объявляем три переменные i, c, g типа int со значениями 0, 5, 10 — их все можно использовать в цикле. Но в нашем случае изменяться будет только переменная i:
При желании можно изменять все три переменных, причём разными способами, в параметре <действие после итерации> цикла for. Например:
Или, в дополнение к этому, даже вызвать метод в третьей части цикла:
Несмотря на то, что параметрами <начальное действие> и <действие после итерации> может быть всё что угодно, чаще всего цикл for применяют в следующих случаях:
- если требуется считать число итераций цикла или нужно использовать значение счётчика итераций — то есть <начальным действием> является объявление переменной, а в <действии после итерации> происходит инкремент/декремент этой переменной;
- когда число итераций известно заранее — происходит обход массива, работа с символами строки, коллекцией и тому подобное.
Если же эта информация нам не нужна или количество итераций нельзя предсказать заранее, то обычно используют цикл while (do…while)
Цикл while (с предусловием)
Этот цикл имеет следующую синтаксическую структуру:
В целом он не сильно отличается от цикла for — цикл while не имеет параметров <начальное действие> и <действие после итерации>, а содержит лишь условие. Это позволяет выполнять <тело цикла> до тех пор, пока выражение в условии возвращает true перед каждой итерацией.
Ранее мы рассматривали пример цикла for, в котором не были указаны параметры <начальное действие> и <действие после итерации>:
Его несложно переписать с использованием while:
Графически алгоритм работы можно представить такой схемой:
В отличие от for, в цикле while нельзя не указывать параметр <условие выполнения цикла>, то есть запрещена такая запись:
Цикл while называется циклом с предусловием, потому что первый раз условие выполнения проверяется перед первой итерацией. Если проверка вернёт значение false, то ни одна итерация не будет выполнена.
Цикл while обычно используется в случаях, когда:
- число итераций не известно заранее;
- счётчик итераций не требуется по логике программы.
Цикл do…while (с постусловием)
Кроме цикла с предусловием while существует вариант, который выполняет хотя бы одну итерацию, а после этого проверяет условие. Это цикл do…while, который называется циклом с постусловием.
Синтаксис do…while:
Сначала отрабатывает действие в <теле цикла>, а потом проверяется <условие выполнения цикла>. Если оно возвращает true, то цикл выполнит действие повторно.
Предположим, что нам обязательно нужно вывести на консоль слово «Привет» хотя бы один раз. Для этого можно использовать следующую конструкцию:
Цикл do…while редко применяется на практике, но его всё же используют:
- если нужно сделать что-то хотя бы единожды — например, вывести слово «Привет», как выше;
- если значение, от которого зависит условие, инициализируется внутри тела цикла.
Пример кода:
С помощью конструкции new Random().nextInt() здесь берётся очередное случайное число. Итог работы таков: цикл будет находить и выводить на консоль случайные числа до тех пор, пока их значение не будет превышать 50.
Диаграмма работы цикла do…while:
Цикл foreach
Для обхода массива или коллекции можно применять циклы for:
Синтаксис цикла foreach:
С его помощью можно переписать приведённые примеры обхода массива и коллекции. Получится такой код:
Результат выполнения обоих вариантов будет одинаковым, но конструкция сильно упростилась — теперь не нужно следить за счётчиком итераций. Цикл foreach сам поочерёдно берёт значения из массива/коллекции и помещает их в указанную перед двоеточием переменную.
Важно, чтобы тип переменной, указанной перед двоеточием, совпадал с типом массива/коллекции.
Внимание: далее материал повышенной сложности (необходимо знание интерфейсов).
На самом деле параметром после двоеточия может быть любой класс, который имплементирует интерфейс Iterable (а все коллекции-наследники java.util.Collection его имплементируют) и реализует метод iterator(). Мы можем даже самостоятельно создать класс, который будет передаваться в качестве параметра.
Пример такого кода:
Мы объявляем класс MyIterable, в нём создаём массив, по которому будем итерироваться. Реализуем метод iterator(), возвращающий объект интерфейса Iterator, а также hasNext и next. Метод hasNext вызывается перед каждой итерацией и проверяет, есть ли следующий элемент. Если не дошли до конца массива, то hasNext вернёт true.
Когда метод hasNext возвращает true, цикл foreach вызывает метод next, который должен вернуть следующий элемент. В нашем случае он, кроме того, увеличивает на 1 текущую позицию элемента массива для последующей итерации.
Под капотом цикл foreach выглядит как простой while, но такой код будет более громоздким:
Оператор break
Внимание: для освоения этого раздела необходимо понимание принципов работы с массивами.
Не всегда нужно, чтобы цикл отработал до конца, — бывают ситуации, когда нам требуется его прервать. Допустим, мы обходим некий массив и хотим остановить выполнение цикла, если в нём найдено число 5. Для таких ситуаций существует оператор break, который полностью прекращает работу цикла.
Вот варианты кода для всех четырёх типов циклов (для цикла do…while дополнительное важное условие — размер массива должен быть больше 0):
Если выполнить эти фрагменты, во всех вариантах на выводе будет одно и то же:
Выполнение циклов полностью прекращается, как только любой из них доходит до позиции 4, где и расположено искомое число 5. Это происходит потому, что везде задано условие: если текущий элемент равен 5, вызывается оператор break.
Оператор continue
Бывают ситуации, когда по логике программы требуется пропустить текущую итерацию. Предположим, нужно, чтобы в консоль выводилась надпись «Текущее число <число>. Позиция <позиция>», но при условии, что текущий элемент массива не равен 5. Это можно сделать с помощью оператора continue — он позволяет сразу перейти к следующей итерации.
Важно: при вызове этого оператора цикл прекращает текущую итерацию, выполняет <действие после итерации> и проверяет условие цикла. Если проверка вернула true, то переходит к следующей итерации, а иначе — заканчивается.
Примеры кода для всех четырёх типов циклов:
Вывод для всех фрагментов будет одинаковым:
Таким образом, мы пропускаем четвёртую позицию в массиве и не выводим число 5 в консоль.
Прервать выполнение цикла может также оператор return, но этот оператор также и выйдет из метода, который выполняется в текущий момент.
Бесконечный цикл
Бесконечный цикл может понадобиться, если мы не знаем условие выхода (оно определено внутри цикла) или условий несколько, либо если нужно, чтобы цикл не заканчивался вовсе.
Есть три варианта того, как создать такой цикл:
или
или
Пример использования бесконечного цикла:
С помощью конструкции new Random ().nextInt () мы на каждой итерации получаем случайное число и записываем его в переменную randomNumber. Если рандомное число равно 5, то выходим из цикла.
Цикл ниже не закончит работу никогда — строка «Привет!» будет выводиться в консоль бесконечно:
С точки зрения эффективности исполнения не имеет значения, какой из вариантов бесконечного цикла мы используем. Если взглянуть на байт-код, для JVM все эти фрагменты будут выглядеть одинаково:
L0
LINENUMBER <line number> L0
FRAME SAME
GOTO L0
Поэтому используйте циклы, которые лучше подходят для решения вашей задачи, чтобы код получался максимально понятным для чтения и удобным для дальнейшего расширения.
Вложенные циклы
Внимание: для освоения этого раздела необходимо понимание принципов работы с массивами.
Часто используют циклы, один из которых выполняется в теле другого, — их называют вложенными. Это может потребоваться для обхода двумерных массивов, генерации данных и много чего ещё. Вкладывать друг в друга можно разные циклы неограниченное количество раз.
Вот примеры кода:
В этом фрагменте был создан двумерный массив chars, по которому мы прошли с помощью одного цикла for, вложенного в другой — тоже for. Для каждой итерации внешнего цикла выполняются все итерации вложенного в него внутреннего. Таким образом, для массива размерности 5 на 5 будет совершено 25 итераций — внешний цикл идёт по строкам, внутренний — по столбцам.
Ещё пример, но теперь уже трёх вложенных циклов:
Тут мы прошлись по значениям из трёх массивов и сгенерировали шесть сообщений с разными приветствиями, именами и вопросами.
Бывают ситуации, когда нужно внутри вложенного цикла прекратить выполнение текущего и того, в который он вложен. Если использовать для этого просто break, мы выйдем только из текущего цикла, а внешний продолжит работать:
Как видно из примера, ожидаемого результата (прекратить работу обоих циклов, если найдено число 5) мы не получили. В таких ситуациях можно использовать, например, проверку с помощью boolean-значения:
Мы вводим во внешний цикл логическую переменную check и присваиваем ей значение false. Если внутри второго цикла работа прекращается оператором break, перед этим check присваивается значение true. После завершения работы вложенного цикла проверяем во внешнем, что находится в нашей переменной check. Если true, значит, вложенный цикл был прерван и требуется прервать текущий.
Конструкция break (continue) с указателем
Существует ещё одно решение, позволяющее прекратить работу нескольких вложенных циклов: операторы break и continue с указателем.
Синтаксис break с указателем:
Используем эту конструкцию для фрагмента кода из прошлого примера:
В результате с помощью указателя (его имя может быть любым) и слова break мы из внутреннего цикла перешли к внешнему и прекратили его работу. Эта конструкция применяется с различными типами циклов: for, while, do…while и foreach.
Вместо оператора break можно использовать continue с указателем. Отличие в том, что с помощью continue мы не прекращаем указанный цикл, а переходим к его следующей итерации. Вот, например, исправленный код из прошлого примера, но с использованием continue:
Примечания:
1. Заключать название цикла в фигурные скобки не обязательно, если его тело состоит всего из одной строки. Но на этом лучше не экономить, потому что при расширении тела цикла (когда вы будете модифицировать программу) про недостающие скобки очень легко забыть. Зато вовремя расставленные {} делают ваш код более читаемым.
2. Оператор break с указателем применим не только к циклам, но и к простым блокам кода. Но делать это не рекомендуется, так как даже блок-схемами очень сложно описать такое поведение программы. Код, где используются break с указателями, становится сложным для чтения и поддержки.
3. По этой же причине операторы break и continue с указателем не рекомендуется использовать и для циклов.
Подытожим
Мы выяснили, как циклы в Java помогают работать с массивами: перебирать значения, находить нужные элементы, запрашивать данные и выполнять другие повторяющиеся действия без дублирования кода — простым и понятным способом.
Узнайте больше о циклах и других элементах Java на нашем курсе «Профессия Java-разработчик». Приходите! Вы научитесь программировать на одном из самых востребованных языков и сможете устроиться на высокооплачиваемую работу.