5. Циклы (Java. Базовый курс)

5. Циклы (Java. Базовый курс)


Date of publication 29.01.2021



На данном уроке разберём все виды циклов, которые есть в Java и посмотрим как их использовать на практике.

Циклы

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

Цикл for

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

Общая форма записи выглядит так:

fоr (инициализация; условие; итерация) { 
	// тело цикла
}

Остановимся подробнее на содержимом в скобках:

инициализация - устанавливается значение переменной управления циклом, или проще говоря начальное значение счётчика.

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

итерация - выражение которое увеличивает или уменьшает значение счётчика на определённую величину.

Чтобы лучше разобраться с циклом for, разберём практический пример: напишем упрощенный вариант программы “Калькулятор вкладов”. Программа рассчитает потенциальный доход вклада через определённое количество лет (расчет по месяцам сложнее и требует дополнительных знаний - будет разобран позже). При этом необходимо вывести списком количество начисленных процентов за каждый год. Для упрощения задачи не будем учитывать капитализацию процентов и налоги.

Входные параметры:

  1. Сумма, внесенная на депозит
  2. Срок размещения денежных средств (количество лет)
  3. Процентная ставка по депозиту (% годовых)

Вот что у меня получилось:

Пример использования цикла for

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

Scanner input = new Scanner(System.in);

System.out.println("Введите сумму, вносимую на депозит: ");
double amount = input.nextDouble();
System.out.println("Введите срок размещения денежных средств (количество лет): ");
int period = input.nextInt();
System.out.println("Введите процентную ставку по депозиту (% годовых): ");
double rate = input.nextDouble();

В строке 18 появилась новая конструкция - объявление цикла for. В качестве управляющей переменной или счётчика выступает переменная i типа int - я её проинициализировал со значением 1. Пока значение счётчика меньше или равно значению переменной period, цикл будет выполняться. После каждой итерации (срабатывание цикла) значение счётчика i будет увеличиваться на одну единицу балгодаря выражению i++:

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

В теле цикла рассчитывается сумма начисленных процентов, которая записывается в переменную profit. Для этого я умножаю сумму (amount) на процентную ставку (rate) и на количество процентных периодов (равно значению переменной i) и делю на 100. Для упрощения задачи в нашем случае один процентный период равен одному году и в цикле соответствует счётчику i.

profit = (amount * rate * i) / 100;
String result = String.format("%.2f", profit);
System.out.println(i + "-й год, сумма начисленных процентов: " + result + " RUB");


Таким образом в первую итерацию будет рассчитан и выведен в консоль доход за 1-й год, во вторую итерацию - за 2 года и так далее, пока значение i не станет больше значения переменной period. В результате получим расчет процентов по каждому году и итоговую сумму остатка на момент закрытия вклада.

Обратите внимание на строку 20:

String result = String.format("%.2f", profit);

Здесь идёт преобразование переменной profit типа double в строку и округляется до 2 знаков после запятой с помощью форматирования по шаблону “%.2f”. Полученный результат записывается в строковую переменную result.

Больше в теле цикла ничего не происходит и кодовый блок закрывается фигурной скобкой. После завершения выполнения цикла for в строке 23 к начальной сумме прибавляется сумма начисленных процентов за все процентные периоды, а в строке 24 выводится результат:

amount += profit;
System.out.println("Остаток средств на депозите: " + amount + " RUB");

Полный листинг программы:

package ru.akutepov;

import java.util.Scanner;

public class DepositCalculator {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        System.out.println("Введите сумму, вносимую на депозит: ");
        double amount = input.nextDouble();
        System.out.println("Введите срок размещения денежных средств (количество лет): ");
        int period = input.nextInt();
        System.out.println("Введите процентную ставку по депозиту (% годовых): ");
        double rate = input.nextDouble();

        double profit = 0;
        for (int i = 1; i <= period; i++) {
            profit = (amount * rate * i) / 100;
            String result = String.format("%.2f", profit);
            System.out.println(i + "-й год, сумма начисленных процентов: " + result + " RUB");
        }
        amount += profit;
        System.out.println("Остаток средств на депозите: " + amount + " RUB");
    }
}

Ну и конечно пример работы написанной программы:

Пример работы программы с циклом for
 

В языке Java есть ещё одна (на самом деле не одна) разновидность цикла for - бесконечный цикл. Общая запись такого цикла выглядит следующим образом:

for( ; ; ) { 
	// тело цикла
}

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

 

Цикл while

Цикл while или цикл с предусловием. Выполняется до тех пор, пока условие истинно, иначе выполнение цикла завершается. У цикла есть интересная особенность: если условие окажется изначально ложным, то цикл не выполнится ни разу - такое часто встречается на практике при обработке различных данных.

Общая форма записи выглядит следующим образом:

while (условие) {
   // тело цикла
}

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

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

Смотрим пример:

Пример использования цикла while

Вначале считываем из консоли две строки и записываем их в переменные text и word. Чтобы приложение работало корректно, нужно перевести все буквы в полученных строках в нижний регистр с помощью метода toLowerCase(), который присутствует в классе String. В противном случае есть вероятность найти не все подстроки (например если слово начинается с заглавной буквы), потому что с точки зрения Java слова “Мир” и “мир” разные. Это связано с тем что буквы “М” и “м” имеют разные коды в таблице символов, поэтому нужно учитывать данную особенность в своём алгоритме:

Scanner input = new Scanner(System.in);

System.out.println("Введите текст: ");
String text = input.nextLine();
System.out.println("Введите нецензурное слово, которое требуется заменить: ");
String word = input.nextLine();

// переводим всё в нижний регистр
text = text.toLowerCase();
word = word.toLowerCase();

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

В строке 22 я объявил цикл while с очень интересным условием: с помощью метода contains класса String я проверяю не содержит ли текст в переменной text слово из переменной word. Если слово найдено в тексте, то метод contains вернёт true и цикл начнёт свою работу, в противном случае цикл не запустится, так как текст не содержит нецензурных слов и править нечего.

int count = 0;
while (text.contains(word)) {

Общая сигнатура метода contains выглядит так:

public boolean contains(CharSequence s)

Данный метод следует вызывать у строки (в нашем случае text), в которой мы хотим найти слово, переданное в переменную s (в нашем случае word), более подробно про методы класса String можно прочитать тут: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html

В строке 23 я заменяю нецензурное слово на значение из константы REPLACEMENT с помощью метода replace класса String. Данный метод ищет первое совпадение с заданным значением и заменяет его на новое значение, после чего завершает работу. После замены найденной подстроки я увеличиваю счётчик слов count на единицу.

int count = 0;
while (text.contains(word)) {
    text = text.replace(word, REPLACEMENT);
    count++;
}

Общая сигнатура метода replace:

public String replace(CharSequence target, CharSequence replacement)

В target передаётся строка (в нашем случае word), которую следует заменить на другую строку из переменной replacement (в нашем случае константа REPLACEMENT).

Отдельно хочу остановиться на константе REPLACEMENT, она объявлена в 7-й строке:

 private static final String REPLACEMENT = "***";

Так как переменная REPLACEMENT объявлена как final, её значение задаётся только один раз при инициализации и далее его изменить невозможно. Такие переменные называются константами и они хранят какое-то одно неизменяемое значение. Имена переменных-констант принято писать в верхнем регистре.

Касательно примера программы: если бы не стояла задача посчитать количество нецензурных слов в тексте, то можно было бы обойтись без использования цикла. В классе String есть метод replaceAll, который ищет все подстроки в строке и меняет их на заданное значение.

Полный листинг программы:

package ru.akutepov;

import java.util.Scanner;

public class Censor {

    private static final String REPLACEMENT = "***";

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        System.out.println("Введите текст: ");
        String text = input.nextLine();
        System.out.println("Введите нецензурное слово, которое требуется заменить: ");
        String word = input.nextLine();

        // переводим всё в нижний регистр
        text = text.toLowerCase();
        word = word.toLowerCase();

        int count = 0;
        while (text.contains(word)) {
            text = text.replace(word, REPLACEMENT);
            count++;
        }

        System.out.println("Найдено и заменено нецензурных слов: " + count);
        System.out.println(text);
    }
}

А вот пример работы программы:
Пример работы программы с циклом while

Чтобы не травмировать вашу психику, я не стал использовать нецензурные слова при тестировании программы :)

 

Цикл do while

Цикл do while или цикл с постусловием. Очень похож на цикл while, но отличается тем, то вначале выполняется тело цикла, а потом проверяется условие. В отличие от while, который может не выполниться ни разу, цикл do while выполняется минимум 1 раз.

Общая форма записи цикла выглядит так:

do {
   //тело цикла
} while (условие);

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

Так как пользователь затратит минимум одну попытку, то для решения идеально подойдёт цикл do while, смотрим пример программы:

Пример использования цикла do while
 

Вначале инициализируется переменная input типа Scanner и переменная random типа Random. C классом Scanner вы уже знакомы, a Random я использую в своих примерах впервые. Класс Random нужен для генерации случайных чисел и как раз с помощью переменной данного типа компьютер будет “загадывать” число.

Scanner input = new Scanner(System.in);
Random random = new Random();

Далее я объявляю ещё 3 переменные: в answer будут записываться ответы пользователя, count - счётчик попыток, value будет хранить число, загаданное компьютером. С помощью метода nextInt из класса Random генерируется случайное число от 0 до заданного значения (не включительно). Соответсвенно чтобы генерировались числа от 1 до 100, нужно прибавить к полученному значению единицу, после чего, полученный результат записывается в value:

int answer;
int count = 1;
int value = random.nextInt(100) + 1;

Сигнатура метода nexInt выглядит следующим образом:

public int nextInt(int bound)

Метод nextInt принимает единственный аргумент - переменную bound типа int. Случайное число будет сгенерировано от 0 до значения переменной bound. Более подробную информацию о методах класса Random можно прочитать тут: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Random.html

С 16-й строки начинается цикл do whilе, в теле которого программа просит пользователя ввести число, а затем проверяем ответ и выводим подсказку, если пользователь ошибся. Так же увеличивается значения счётчика count на единицу. Цикл закончит свою работу только тогда, когда пользователь введёт верное число и значение переменной answer будет равно значению переменной value:

do {
    System.out.println("Угадайте число, которое загадал компьютер. Попытка №" + count);
    answer = input.nextInt();
    if (answer < value) {
        System.out.println("Не угадали, ваше число меньше загаданного");
    } else if (answer > value) {
        System.out.println("Не угадали, ваше число больше загаданного");
    }
    count++;
} while (answer != value);

Как только сработает условие и цикл перестанет выполняться, в консоль будет выведено сообщение о победе, так как пользователь угадал число.

Полный листинг программы:

package ru.akutepov;

import java.util.Random;
import java.util.Scanner;

public class GuessTheNumber {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        Random random = new Random();

        int answer;
        int count = 1;
        int value = random.nextInt(100) + 1;

        do {
            System.out.println("Угадайте число, которое загадал компьютер. Попытка №" + count);
            answer = input.nextInt();
            if (answer < value) {
                System.out.println("Не угадали, ваше число меньше загаданного");
            } else if (answer > value) {
                System.out.println("Не угадали, ваше число больше загаданного");
            }
            count++;
        } while (answer != value);

        System.out.println("Победа! Вы угадали число!");
    }
}

Попробуйте поиграть в эту игру и посмотрите за сколько попыток получится угадать число :) Вот мой пример игры:

Пример работы программы "Угадай число"

 

Оператор break

Оператор break позволяет прервать выполнение цикла, если возникла такая необходимость. В Java есть две разновидности данного оператора: оператор break бывает помеченным и непомеченным. Непомеченный break используется для прерывания цикла, в котором был вызван данный оператор. Помеченный break используется в конструкции со вложенными циклами и позволяет прерывать внешние циклы.

Примеры с помеченным и непомеченным break:

Пример использования операторов break

Для удобства отдельно привожу листинг программы:

package ru.akutepov;

import java.util.Scanner;

public class BreakExample {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        // цикл с непомеченным break
        while (true) {
            System.out.println("Введите 1, чтобы выйти из цикла");
            int value = input.nextInt();
            if (value == 1) {
                break;
            }
        }

        // циклы с помеченным break
        out: for (int i = 1; i < 100; i++) {
            System.out.println("i = " + i);
            for (int j = 1; j < 100; j++) {
                System.out.println("j = " + j);
                System.out.println("Введите 1, чтобы выйти из цикла");
                int value = input.nextInt();
                if (value == 1) {
                    break out;
                }
            }
        }

    }
}

Первый цикл будет прерван непомеченным оператором break, как только пользователь введёт значение 1. 

В примере с помеченным break будут прерваны сразу оба цикла, так как break завершает работу внешнего цикла for, хотя и вызывается из внутреннего цикла for.

 

Оператор continue

Оператор continue позволяет завершить текущую итерацию цикла преждевременно и перейти к следующей итерации, при этом работа цикла не прерывается. Как и оператор break, оператор continue в Java так же бывает помеченным и непомеченным. Непомеченный continue используется для завершения итерации цикла, в котором был вызван данный оператор. Помеченный continue используется в конструкции со вложенными циклами и позволяет завершать итерации внешних циклов.

Примеры с помеченным и непомеченным continue:

Пример использования оператора continue
 

В примере с непомеченным continue итерация будет завершена, если пользователь введёт число 1. Соответственно сообщение из строки 17 не будет выведено в консоль, так как весь код ниже continue будет пропущен.

В примере с помеченным continue выполнение внутреннего цикла будет прервано полностью, так как будет осуществлён переход к следующей итерации внешнего цикла for. Сообщение из строки 30 так же не будет выведено в консоль в случае срабатывания continue.

Помеченные break и continue на практике используются не часто, но помнить о них нужно.

Полный листинг программы:

package ru.akutepov;

import java.util.Scanner;

public class ContinueExample {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        // цикл с непомеченным continue
        for (int i = 0; i < 10; i++) {
            System.out.println("Введите 1, чтобы завершить итерацию");
            int value = input.nextInt();
            if (value == 1) {
                continue;
            }
            System.out.println("Пользователь ввёл число " + value);
        }

        // циклы с помеченным continue
        out: for (int i = 1; i < 10; i++) {
            for (int j = 1; j < 10; j++) {
                System.out.println("Введите 1, чтобы завершить итерацию");
                int value = input.nextInt();
                if (value == 1) {
                    continue out;
                }
                System.out.println("Пользователь ввёл число " + value);
            }
            System.out.println("i = " + i);
        }

    }
}


Домашнее задание:

  1. Напишите программу для вычисления факториала числа. Факториал числа представляет собой произведение всех натуральных чисел от 1 до этого числа включительно. Например, факториал числа 7 выглядит так: 1 * 2 * 3 * 4 * 5 * 6 * 7
  2. Выведите на экран ряд чисел Фибоначчи, состоящий из n элементов (значение n вводит пользователь). Числа Фибоначчи – это элементы числовой последовательности 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …, в которой каждое последующее число равно сумме двух предыдущих.
  3. Пользователь вводит последовательно n чисел. Вывести в консоль максимальное число, которое ввёл пользователь.

< Предыдущий урок    Следующий урок >