logo
Ещё

Как использовать циклы в языке Java

Циклы – неотъемлемая часть языка программирования, без них вам приходилось бы перебирать значения массива из 100 элементов вручную, сотней строк кода. В Java циклы есть на все случаи жизни: вам доступны циклы for, for each, while и do while. Этим, кстати, могут похвастаться не все языки программирования: оператором for each не обладает С, циклы do-while недоступны в Python (в обоих случаях недоступные инструменты приходится эмулировать, что крайне неудобно).

Ниже – о том, как все эти циклы работают, где их лучше использовать и в каких случаях циклы while и for причиняют больше вреда, чем пользы.

Необходимость и польза циклов

Нередко перед программистом встает задача выполнить одно и то же действие многократно. Самый простой пример – написание шести подряд слов «Привет». Чтобы решить ее, требуется написание следующего программного кода.

System.out.println("Привет");

System.out.println("Привет");

System.out.println("Привет");

System.out.println("Привет");

System.out.println("Привет");

System.out.println("Привет");

Кажется, что в дублировании одинаковой части кода нет ничего сложного. Но только в том случае, если речь идет об ограниченном количестве повторов. Если их число увеличивается до полусотни, сотни и т.д., простым копирование уже не обойтись – слишком большие для этого потребуются трудозатраты.

В подобной ситуации намного проще и правильнее воспользоваться циклом. В этом случае исходный код для описанного выше примера становится заметно короче и приобретает такой вид.

for (int i = 0; i < 6; i++) {

System.out.println("Привет");

}
Важно отметить, что использование цикла фактически не ведет к увеличению продолжительность программного кода и для 6, и для 100, и для любого другого числа итераций. Что очень удобно и серьезно экономит силы и время программиста. Все изменение касается цифры, которая указывается внутри приведенной выше структуры (в данном примере – вместо «6»).

Виды циклов

Для программирования Java, как мы уже говорили, используются 4 вида циклов:

  • While. Пока выполняется заданное программистом условие, цикл старательно выполняет те строчки кода, которые помещены внутрь цикла. Проверка этого условия происходит до того, как поток исполнения входит в цикл – то есть когда очередь доходит до while, сначала проверяется условие, а потом уже исполняется итерация (проход по всем строкам цикла).
  • Do while. То же, что и while, только условие пишется в конце цикла, а не в начале. Поэтому один раз цикл исполнится в любом случае, а вот будет ли он исполнен повторно – зависит от того, соблюдается ли условие.
  • For. Если While будет исполняться, пока условие верно, то for будет исполняться определенное количество раз. Это самое количество раз вы задаете в условиях цикла тремя параметрами.
  • For each. «Новомодный» for для итерируемых объектов, вроде списков и хэш-таблиц (=словарей). Проблема обычного for – в том, что он умеет перебирать только числовые значения, что хорошо для массива, но неприменимо для словаря. For each перебирает все элементы коллекции, при этом каждый обрабатывается строго один раз.


Теперь перейдем к более детальному рассмотрению.

Работа While

Конструкцию while пишут вот так:

while (something) {

statement_1;

statement_2;

}


Something – это условие, которое должно возвращать true или false: x > 5, например. Можно и просто написать while (true), так организовывается бесконечный цикл. О том, как управлять исполнением бесконечных циклов (через операторы break), мы расскажем ниже.

Statement – это какие-то строки, которые нужно исполнить. Итерация – это когда все statement цикла исполнены. Условие проверяется 1 раз за итерацию, то есть если у вас есть 5 строк кода внутри цикла – сначала у цикла выполнятся все 5 строк, а потом он проверит, выполняется ли условие, и примет решение о том, нужно ли ему выполняться дальше.

Циклы while очень полезны тогда, когда вам нужно выполнять что-то, пока не наступят определенные условия. Например, вы пишете программу, которая принимает от пользователя какой-то символ, который он вводит через консоль, и делает что-то в зависимости от введенного символа. Если пользователь вводит символ ‘q’, то работа программы останавливается:

char input_value = ‘ ‘;

while (input_value != ‘q’) {

System.out.println("Input a character: ");

Input_value = (char) System.in.read();

//делаем что-то, исходя из символа в input_value

…

}

Как вы можете заметить, здесь есть одна проблема – процесс инициализации выполняется до первой итерации цикла, то есть мы пишем

char input_value = ‘ ’
и запихиваем в переменную пробел.

Это – не очень хорошая практика, потому что потом мы можем изменить формат ввода, пробел будет просачиваться в первую итерацию и выполнять какую-то команду, завязанную на пробел – получим сложный в поиске баг.

Чтобы исправить ситуацию, будем использовать циклы do while:

char input_value;

do {

System.out.println("Input a character: ");

Input_value = (char) System.in.read();

//делаем что-то, исходя из символа в input_value

…

} while (input_value != ‘q’);

Как видите, теперь на месте условия стоит do, а само условие переместилось вниз. Оператору do все равно, верно ли условие – в первый раз цикл будет запущен в любом случае. Поэтому инициализация переменной input_value у нас теперь не включает присвоение пробела – в переменную сразу будет записано значение, введенное с клавиатуры.

Работа For

С for все проще и сложнее одновременно. Сам for выглядит вот так:

for (init; termination_condition; incr) {

statement_1;

statement_2;

…

}

Сложнее – потому что нужно задавать больше условий в шапке цикла. В init выполняется инициализация переменной, которая будет счетчиком; в termination_condition указано условие для прерывания циклов; в incr указывается, как должны себя вести переменные счетчика, когда итерация подойдет к концу. Как это все работает:

  1. Поток исполнения доходит до инструкции for.
  2. Создается переменная init.
  3. Делается проверка условия termination_condition.
  4. Если условие возвращает true, то последовательно исполняются все statements.
  5. Выполняется incr, после чего – переход к шагу 3.


Как это выглядит в реальной жизни:

for (int i = 1; i < 10; i++) {

System.out.println("Итерация " + i);

}

Цикл будет выводить: «Итерация 1», «Итерация 2», …, «Итерация 9». После того, как «Итерация 9» выведется, к i прибавится еще 1, и, поскольку 10 < 10 = false, цикл закончится.

Отметим, что инициализация переменной, условие прерывание и инкремент – опциональные, можно писать for (;;). Инициализация счетчика не нужна, если вы пользуетесь уже существующей переменной, вместо условия остановки можно использовать прерывания итерации через break, а инкремент можно делать прямо внутри цикла, поскольку i – это такая же переменная, как и остальные. Но использовать for без параметров следует осторожно, потому что от цикла for ждут конечного количества повторения цикла, и ваш код может быть сложно прочитать, если вы используете for нестандартно.

For each выглядит так:

for (Type variable : iterable) {

statement_1;

statement_2;

…

}

Iterable – это объект, который мы собираемся перебирать. Variable – это переменная, в которую мы будем класть каждый элемент перебираемого объекта. После того, как for кладет в variable элемент, с этим элементом можно что-то делать – для этого есть statement_1 и остальные. Когда все элементы перебраны, цикл заканчивается, то есть бесконечного цикла здесь быть не может.

Использование цикла For требует дополнительного учета нескольких существенных нюансов, к числу которых относятся такие:

  1. Применяемая в качестве параметра цикла переменная не должна задействоваться за пределами данного участка исходного кода. Так как программа ее попросту не заметит из-за ограниченности области видимости телом цикла. Если такая необходимость существует, требуется объявлять переменную отдельно, причем за границами цикла.
  2. Действия, размещенные внутри цикла For, не являются обязательными. Можно не указывать их, главное – соблюсти структуру указанием двух точек с запятой.
    int i = 0;
    
    for (; i < 10; i++) {
    
    System.out.println(i);
    
    }
  3. В качестве начального действия цикла допускается указывать любой параметр. Основное требование – он должен быть первым внутри скобок.
  4. Допускается назначение первым действием цикла For составной команды, которая формируется из нескольких отдельных. Пример такого кода показан ниже.
    int any = 0;
    
    int i = 0;
    
    for (someMethod(), any += 2, i++; i < 10; i++) {
    
    //code
    
    }

Работа Do…While (с постусловием)

Такой формат цикла несколько отличается от описанного выше While и называется с постусловием. Его базовый синтаксис выглядит так.

do {

<тело цикла>

} while (<условие выполнения цикла>);

Порядок выполняемых действий имеет следующий вид:

  1. Сначала осуществляется действие, указанное в теле цикла.
  2. Затем происходит проверка условия выполнения.
  3. Если выдается значение true, цикл запускается повторно.


Бесконечный цикл

Такой вариант циклического процесса запускается в том случае, когда нет информации об условии выхода (так как она размещается внутри цикла) или таких условий несколько. Аналогичная необходимость возникает в том случае, когда требуется, чтобы цикл никогда не заканчивался, хотя на практике подобная ситуация складывается достаточно редко.

Цикл создается одним из трех способов, причем все они имеют примерно одинаковую эффективность, что упрощает выбор подходящего. Первый выглядит так:

while (true) {

<тело цикла>

}

Второй имеет следующий вид:

for (;;) {

<тело цикла>

}

Третий и последний:

do {

<тело цикла>

} while(true);

В качестве наглядного примера бесконечного цикла, выполнение которого не должно прекратиться, можно привести такой. Его запуск приведет к тому, что приветствие будет появляться на консоли до ее выключения или перегрузки системы.

while (true) {

System.out.println("Привет!");

}

Вложенный цикл

Вложенным называют цикл, тело которого находится внутри другого. Такой формат исходного кода требуется в том случае, когда необходима работа с двухмерными массивами. Примером программы выступает такая.

char[][] chars =

{{'x', ' ', ' ', ' ', 'x'},

{' ', 'x', ' ', 'x', ' '},

{' ', ' ', 'x', ' ', ' '},

{' ', 'x', ' ', 'x', ' '},

{'x', ' ', ' ', ' ', 'x'}};

for (int i = 0; i < chars.length; i++) {

for (int j = 0; j < chars[i].length; j++) {

System.out.print(chars[i][j]);

}

System.out.println();

Чтобы заниматься написанием вложенных циклов, необходимо не только знание языка программирования Java. Требует еще и навык работы с массивами и понимание базовых принципов их функционирования.

Управление исполнением: break, continue

Continue и break используются, когда вам нужно управлять потоком исполнения непосредственно внутри цикла. Операторы continue досрочно завершают текущую итерацию и возвращают управление к проверке условия, оператор break вообще завершает выполнение цикла. Хрестоматийный пример с continue:

for (int i = 1; i <= 10; i++) {

if (i % 2 == 0) continue;

System.out.println(i + " ");

}

Здесь continue используется для того, чтобы прервать выполнение итерации, если i – четное число. В консоль выводится 1, 3, 5, 7, 9. Когда поток исполнения натыкается на строку кода с проверкой на четность и убеждается, что i – четное, он исполняет continue, и остальная часть итерации не выполняется.

Теперь пример с break:

for (int i = 1; i <= 10; i++) {

if (i >= 7) break;

System.out.println(i + " ");

}

Здесь будет выведено 1, 2, 3, 4, 5, 6. Когда i доходит до 7, условие внутри цикла становится истинным, и срабатывает break, после чего управление принудительно передается следующему блоку кода (сразу после цикла).

Чем опасны циклы?

Циклы – это отличный инструмент, если вам нужно сделать большое или неизвестное количество одинаковых операций. Но именно циклы чаще всего приводят к переполнению стэка, а тот, в свою очередь, приводит к аварийному завершению программы. И виной тому – вложенные циклы и бесконечные циклы.

Вложенные циклы опасны потому, что увеличивают количество итераций в геометрической прогрессии. Например, вам нужно найти все корни уравнения x^2 + y^2 = z^2, x/y/z <= 1000. Самый простой способ решения – запустить 3 цикла, самый верхний перебирает x, средний перебирает y, внутренний цикл перебирает z. Поскольку нам нужно найти все корни, циклы должны исполниться полностью. Сколько операций будет произведено? 1000 * 1000 * 1000 = 1 000 000 000. Миллиард операций – много, но еще терпимо. Теперь предположим, что вы ищете все корни уравнения x^2 + y^2 + z^2 = n^2, x/y/z/n <= 1000. Сколько нужно совершить операций? 1 000 000 000 000. Здесь ваш компьютер уже серьезно задумается. И это мы не берем случаи, когда внутри циклов производятся собственные сложные вычисления, возведение в квадрат – это довольно простая операция.

Вторая проблема – это бесконечный цикл. Вообще, бесконечный цикл бывает контролируемым и неконтролируемым. Контролируемый бесконечный цикл – это когда вы специально его создали и у вас все под контролем (ну, или вы по крайней мере так думаете). Такие бесконечные циклы очень полезны – например, они используются в web-серверах, когда сервер вечно (пока подается питание) ждет запроса на соединение. Но есть и другие бесконечные циклы – те, которые заставляют систему зависнуть, бесконечно исполняя какой-то код. Например, вы написали цикл for, в котором с каждой итерацией увеличиваете i на единицу. Но где-то внутри цикла вы случайно написали i--. Что произойдет? Цикл начался, i = 1. Поток исполнения дошел до i--, уменьшил переменную до 0. Итерация завершилась, for увеличил i до 1. i-- снова уменьшил i до 0 – и это будет продолжаться бесконечно.

Именно поэтому в циклах for настоятельно рекомендуется заводить под счетчик новую переменную i прямо в шапке цикла и нигде, кроме секции инкремента, эту i не трогать.

Что почитать?

Вывод

  • Цикл – это инструкция, которая позволяет исполнить определенный блок кода несколько раз либо исполнять его до достижения какого-то условия.
  • В Java есть 4 вида циклов: while, do… while, for, for each.
  • While – исполняется до выполнения определенного условия. Отличие do… while – в том, что цикл гарантированно исполняется 1 раз.
  • For – исполняется определенное количество раз. For each применяется к итерируемым объектам, в которых поочередно перебирается каждый элемент.
  • Чтобы управлять потоком исполнения, можно использовать команды continue (перейти к следующей итерации) и break (выйти из цикла).

Часто ищут