Создание повторяющихся задач — обычное дело в программировании. Один из вариантов — написать один и тот же (или похожий) код столько раз, сколько раз нужно выполнить ту или иную задачу. Такой подход далек от совершенства, потому что большое количество дублирующегося кода трудно читать и поддерживать. Гораздо удобнее и лучше использовать циклы.
Циклы — это структуры для управления повторяющимся потоком программы. Типичный цикл состоит из двух частей. Первая часть представлена логическим условием для управления. Вторая часть — блок кода, который будет выполняться, пока условие истинно или пока условие не станет ложным. Булевое условие пересматривается при каждом запуске блока кода. Критерии выхода из цикла бывают разные для каждого типа цикла, и мы разберем это в сегодняшнем мануале.
Существует два основных типа циклов: while и for. Тип зависит от синтаксиса и логики. Циклы while зависят от логического условия. Это условие может быть общим и пока оно истинно, блок кода выполняется повторно. Циклы for также многократно выполняют блок кода, но условие цикла обычно зависит от увеличения или уменьшения целочисленной переменной. Цикл прерывается, когда эта переменная достигает определенного целевого значения.
В этом мануале мы научимся создавать повторяющиеся задачи с помощью циклов while и for и разберем преимущества и недостатки каждого из них.
Требования
Для выполнения этого мануала вам понадобится:
- Среда в которой можно выполнять программы Java. Чтобы установить ее на локальной машине потребуется:
- Установка Java (версия 11 или выше) с компилятором Java Development Kit (JDK). Для Ubuntu и Debian выполните разделы Варианта 1 в мануале по установке Java с помощью Apt в Ubuntu. Для других операционных систем (включая Mac и Windows), следуйте этим рекомендациям.
- Компилировать и выполнять примеры кода мы будем с помощью запускаемой из командной строки утилиты Java Shell (она же JShell), которая представляет собой цикл чтения-оценки-печати (REPL). Перед началом работы с JShell рекомендуем ознакомиться с гайдом.
- Знакомство с Java и объектно-ориентированным программированием. Подробнее об этом можно почитать в мануале Как написать свою первую программу на Java/
- Знание типов данных Java.
Циклы while
Циклы while отслеживают общий логический условный оператор. Например, условный оператор может проверять, равны ли две целочисленные переменные, являются ли два объекта одинаковыми, связанными, ненулевыми или какими-либо еще, что можно проверить при помощи вычислений. Фактически в этих циклах можно использовать любой булевой оператор, поэтому циклы while универсальные и очень мощные.
В этом разделе с помощью ключевого слова while мы создадим ваш первый программный цикл на Java. Для управления циклом мы будем использовать одну переменную int. Переменная int будет называться x и иметь начальное значение 3. Цикл будет продолжать выполнять блок кода, пока x будет больше 0. В этом блоке кода значение x будет выводиться и, что особенно важно, постдекрементироваться (с помощью оператора –) при каждом выполнении. Операция постдекремента завершит текущий цикл после нескольких выполнений. Без неё цикл будет продолжаться бесконечно.
Примечание: Чтобы следовать примеру кода в этом туториале, с помощью команды jshell откройте утилиту JShell в локальной системе. Затем можно копировать, вставлять или редактировать примеры — просто пишете все после префикса командной строки jshell> и нажимаете ENTER. Для выхода из jshell введите /exit.
Чтобы проверить этот код, вставьте следующие строки в jshell:
int x = 3; while (x > 0) { System.out.println("x is " + x--); }
В первой строке мы определяем переменную x.
Сам цикл начинается со второй строки, с ключевого слова while. Условный оператор (x > 0) управляет циклом. Он сравнивает x и 0 и выясняет, что больше. После него открывается скобка {, которая обозначает начало блока кода. Этот блок будет выполняться несколько раз, пока условие (x > 0) истинно.
Оператор System.out.println(“x is ” + x–); в строке 3 выводит текущее значение x с помощью метода println(). Внутри аргумента println() x постдекрементируется на 1 с помощью x–. Таким образом, с каждым выполнением x уменьшается на 1.
Блок кода заканчивается закрывающей скобкой }. Она определяет конец цикла.
После запуска этого кода в jshell будет следующий вывод:
x ==> 3 x is 3 x is 2 x is 1
Первая строка подтверждает, что x получил значение 3. В следующих строках значение x выводится три раза, каждый раз уменьшаясь на 1. То есть в цикле получается только три итерации, где x равно 3, 2 и 1. Цикл прерывается, когда x достигает 0, потому что условное утверждение x > 0 больше не выполняется. В этот момент цикл завершается, и на экран больше ничего не выводится.
Предыдущий пример — это обычный способ создания цикла. Цель цикла понятна, а условие выхода из него простое: x > 0. Теоретически можно усложнить условие, добавив дополнительные переменные и сравнения (например, x > 0 и y < 0), но с точки зрения чистоты кода это не лучший вариант. Сложные условия делают код трудным для понимания, а пользы от них мало, если она вообще будет.
Циклы do-while
Цикл do-while широко используется, хотя он не такой распространенный, как циклы while. Цикл do-while похож на цикл while, но они отличаются одним ключевым аспектом: do-while сначала выполняет блок кода, а затем оценивает условие цикла.
Напомним, что циклы while сначала оценивают условие и могут вообще не выполнить блок кода, если не выполняется условие. Но в циклах do-while всегда происходит по крайней мере одно выполнение блока кода до того, как будет оценено управляющее условие цикла. Если условие истинно, цикл будет продолжать дополнительные итерации до тех пор, пока оно не перестанет быть таковым. Поэтому циклы do-while полезны при работе с вопросами: цикл завершается только при правильном ответе.
В этом разделе мы создадим цикл do-while, аналогичный циклу while из предыдущего раздела. Числовая переменная будет называться x и иметь начальное значение 3. Но на этот раз условие будет обратным (x < 0) и блок кода будет выполняться до того, как условие будет оценено.
Откройте jshell и добавьте цикл do-while:
int x = 3; do { System.out.println("x is " + x--); } while (x < 0);
Этот код немного похож на цикл while из предыдущего раздела. Во-первых, здесь мы также объявляем переменную x, которая снова равна 3.
В строке 2 цикл начинается с ключевого слова do и открывающей скобки {. В третьей строке значение x выводится методом println(). Метод println() также уменьшает значение x с помощью оператора постдекремента –.
В последней строке находится закрывающая скобка } для блока кода, за которой следует ключевое слово while с условным оператором (x < 0). Именно эта часть отличается от предыдущего цикла while. Здесь условие обратное: x должен быть меньше 0. В предыдущем примере условие было, чтобы x был больше 0. Это изменение показывает, что блок кода будет выполняться один раз, даже если условие никогда не будет выполнено.
После запуска этого кода в jshell вывод будет следующим:
x ==> 3 x is 3
Первая строка подтверждает, что значение x равно 3. Вторая строка выводит x is 3. Это особенность цикла do-while: даже если условие (x < 0) не выполняется, блок кода все равно выполняется хотя бы один раз. Такое действие не всегда нужно, поэтому при использовании циклов do-while нужно быть осторожными. Из-за этого нюанса циклы do-while используются не так часто.
Циклы for
Цикл for — еще один вид циклов. Он также используется для многократного запуска блока кода при определенном условии, но по сравнению с циклом while имеет больше возможностей. В условия управления циклом можно добавить временную переменную, определить условие управления и изменить значение временной переменной. Таким образом все управляющие циклом факторы связываются, что снижает вероятность что-то упустить или допустить ошибку. Это позволит сохранить код чистым и понятным. Цикл for больше подходит для написания кода в более строгом стиле.
Вот типичный цикл for:
for (int x = 3; x > 0; x--) { System.out.println("x is " + x); }
Цикл начинается с ключевого слова for в первой строке. Условный оператор в скобках состоит из трех частей:
- int x = 3 определяет переменную (x). Определить переменную здесь удобно: она используется только в цикле, поэтому есть смысл интегрировать ее в него. Обратите внимание: когда мы использовали цикл while, то переменную приходилось объявлять отдельно перед циклом.
- x > 0 — это условие, которое должно быть выполнено для выполнения блока кода. В этом случае x должен быть больше 0. Эта часть аналогична оператору управления циклом while, который содержит только оцениваемое условие.
- x– это действие, выполняемое во время каждой успешной итерации. В данном случае операция постдекремента уменьшает значение x после каждого успешного выполнения.
Оператор System.out.println(“x is ” + x); в строке 2 выводит значение x с помощью метода println(). Он получает значение x без изменений. А в цикле while мы использовали метод println() для управления значением. Однако в данном случае в этом нет необходимости, поскольку операция постдекремента уже создана в третьей части условного оператора: x–.
Строка 3 завершает блок кода и всю структуру цикла закрывающей скобкой }.
После запуска кода в jshell получим следующий вывод:
x is 3 x is 2 x is 1
Вывод подтверждает, что блок кода был выполнен три раза. В первый раз значение x равно 3. Во второй раз x равно 2, а в третий раз x равно 1. После третьего раза x становится равным 0 и цикл завершается без выполнения блока кода (который выводит значение х).
Цикл for дает тот же результат, что и цикл while. С точки зрения производительности и использования ресурсов разницы быть не должно, поэтому выбор цикла — это в основном вопрос личных предпочтений. Но если количество итераций известно, лучше использовать цикл for, поскольку он точнее.
Циклы foreach
Цикл foreach похож на другие циклы, но его преимущество в том, что он разработан для итерации по группе значений с минимальным количеством кода. Если у вас есть группа значений, вы можете перебирать их без необходимости отслеживать их количество или следить за ходом итерации. Цикл foreach гарантирует, что если в группе есть значения, они будут извлечены одно за другим и представлены на экране.
В большинстве языков программирования для цикла foreach есть удобное сокращение. В Java нет специального ключевого слова foreach, вместо него используется ключевое слово for. Но условный оператор foreach отличается от обычного цикла for, что мы далее и рассмотрим.
Для работы с циклами foreach необходимо использовать группу значений, и для этого подходит массив. Массив — это объект, в котором содержатся значения одного типа. В следующем примере мы будем использовать массив со значениями int.
Добавьте следующий код для цикла foreach в jshell:
int[] numbers = {0, 1, 2}; for (int x: numbers) { System.out.println(x); }
В первой строке создается массив int, в котором содержится три целых числа (0, 1 и 2).
Цикл foreach начинается со строки 2, с ключевого слова for. Затем мы определяем временную переменную int x, за которой следует двоеточие. Двоеточие используется как ярлык для операции foreach.
Примечание: Оператор цикла foreach уникален, поскольку это единственное место в Java, где используется двоеточие.
После двоеточия следует набор значений, например массив.
После оператора цикла foreach идет блок кода, который будет выполняться для каждого значения массива numbers. В этом случае оператор System.out.println(x); выведет значение x с помощью метода println().
После выполнения кода в jshell вывод будет следующий:
numbers ==> int[3] { 0, 1, 2 } 0 1 2
Первая строка подтверждает, что мы определили массив под названием numbers, в котором содержатся значения 0, 1 и 2. Следующие строки выводят значения массива, каждое в новой строке: 0, 1 и 2.
Использование других циклов — это в основном вопрос личного выбора, цикл foreach — де-факто стандарт для итерации по группе значений. Можно также создать свою альтернативу циклу foreach с помощью цикла for или while, но это того не стоит и требует глубокого понимания структур данных.
Бесконечные циклы
Бесконечные циклы (infinite loops) — это циклы for или while, которые никогда не завершаются. Они становятся бесконечными, если условие управления всегда true.
Пример бесконечного цикла while:
while (true) { System.out.println("This will print forever."); }
Первая строка выглядит как обычный цикл while, но в нем использовано простое значение true вместо условия x > 0. В этом случае при оценке условия значение true всегда возвращается напрямую. Здесь нет выхода из цикла, поэтому он будет продолжаться бесконечно. Вторая строка содержит оператор System.out.println(“This will print forever.”); он с помощью метода println(). выводит текст This will print forever.
После запуска приведенного выше кода в jshell следующий вывод будет отображаться на экране до тех пор, пока вы не завершите код сами. Будьте готовы быстро завершить его комбинацией клавиш CTRL+C или просто закрыть терминал. В противном случае это будет продолжаться бесконечно:
This will print forever. This will print forever. ...
Выше мы показали только первые два повторения, но этот вывод будет продолжаться до тех пор, пока вы не нажмете CTRL+C, чтобы завершить его вручную.
В приведенном выше примере есть явное намерение создать бесконечный цикл, потому что вместо логического оператора там просто указано значение true. Раньше такие циклы были распространены, они до сих пор могут встречаться в старых проектах. Конечно, создавать такие бесконечные циклы можно и сейчас, но есть более подходящие способы создания непрерывно выполняющихся задач, например, с помощью Timer. Задачи Timer предпочтительнее, потому что они более сложные и предлагают больше возможностей.
К сожалению, иногда бесконечные циклы создаются непреднамеренно. Вызвать бесконечный цикл можно по ошибке. Рассмотрим следующий код:
int x = 3; while (x > 0) { System.out.println("x is " + x); }
Приведенный выше код похож на первый пример этого мануала. В первой строке вы определяете переменную x со значением 3.
В строке 2 мы видим ключевое слово while и условие управления, в котором говорится, что цикл должен продолжаться пока условие x > 0 истинно.
Оператор System.out.println(“x is ” + x); в строке 3 выводит значение x с помощью метода println(). Но в отличие от цикла while в первом разделе этого мануала, где цикл завершился после трех запусков, в этом примере нет операции постдекремента для x. Поэтому при выполнении этого цикла в jshell получим следующий непрерывный вывод:
x is 3 x is 3 …
Эти строки будут повторяться бесконечно, пока вы не завершите цикл, нажав CTRL+C. Такие непреднамеренные бесконечные циклы опасны, поскольку они могут привести к сбою программы или перегрузке машины, на которой выполняется код. Поэтому следует быть осторожным при создании циклов и всегда проверять, что могут ли они завершить работу. К примеру, чтобы завершить предыдущий цикл, нужно сделать так, чтобы x уменьшался с каждой итерацией.
Подводим итоги
В этом туториале мы разобрались, как создавать повторяющиеся задачи с помощью разных видов циклов. Также вы узнали, когда лучше использовать циклы while и for, и рассмотрели несколько примеров кода. Кроме того, мы поговорили о передовых методах поддержки чистого кода и о том, как избежать распространенных ошибок в циклах.