1.5. Управляющие конструкции
if/else, switch, for, while, do-while
Материалы
Способность выполнять код в зависимости от условий и повторять код пока условие истинно - это базовые строительные блоки большинства языков программирования. Давайте разберёмся, как управлять потоком выполнения в Java.
Условный оператор if
Ваш первый if
Оператор if позволяет запускать код только если условие истинно. Давайте создадим простой пример:
public class Main {
public static void main(String[] args) {
int number = 3;
if (number < 5) {
System.out.println("число меньше 5");
}
}
}
Если вы запустите этот код, вы увидите:
число меньше 5
Условие number < 5 проверяется, и если оно истинно, код внутри фигурных скобок выполняется. Если бы мы изменили number на 7, ничего не было бы выведено.
Обработка альтернативы с else
Часто нужно выполнить один блок кода если условие истинно, и другой блок если ложно. Для этого используется else:
int age = 16;
if (age >= 18) {
System.out.println("Вы можете голосовать");
} else {
System.out.println("Вы пока не можете голосовать");
}
Вывод:
Вы пока не можете голосовать
Поскольку age равен 16, условие age >= 18 ложно, поэтому выполняется блок else.
Множественные условия с else if
Что если нужно проверить несколько условий? Используйте else if:
int score = 75;
if (score >= 90) {
System.out.println("Оценка: A");
} else if (score >= 80) {
System.out.println("Оценка: B");
} else if (score >= 70) {
System.out.println("Оценка: C");
} else if (score >= 60) {
System.out.println("Оценка: D");
} else {
System.out.println("Оценка: F");
}
Вывод:
Оценка: C
Java проверяет каждое условие по порядку и выполняет только первый подходящий блок. Даже если несколько условий истинны, выполнится только первое совпавшее.
Примечание: Использование слишком много
else ifможет усложнить код. В таких случаях рассмотрите использованиеswitch.
Оператор switch
Когда нужно сравнить одно значение с множеством вариантов, switch может быть более читаемым чем цепочка if-else.
Классический switch
Вот пример с днями недели:
int day = 3;
switch (day) {
case 1:
System.out.println("Понедельник");
break;
case 2:
System.out.println("Вторник");
break;
case 3:
System.out.println("Среда");
break;
default:
System.out.println("Другой день");
}
Вывод:
Среда
Ключевое слово break важно! Без него выполнение “провалится” в следующий case.
Проваливание (Fall-through)
Иногда проваливание полезно для группировки случаев:
String month = "Январь";
switch (month) {
case "Декабрь":
case "Январь":
case "Февраль":
System.out.println("Зима");
break;
case "Март":
case "Апрель":
case "Май":
System.out.println("Весна");
break;
default:
System.out.println("Другой сезон");
}
Все три зимних месяца приведут к выводу “Зима”.
Современный switch (Java 14+)
Java 14 добавила более удобную форму switch - switch-выражения. Они возвращают значение и не требуют break:
int dayNum = 3;
String dayName = switch (dayNum) {
case 1 -> "Понедельник";
case 2 -> "Вторник";
case 3 -> "Среда";
case 4 -> "Четверг";
case 5 -> "Пятница";
case 6 -> "Суббота";
case 7 -> "Воскресенье";
default -> "Неверный день";
};
System.out.println(dayName); // Выведет: Среда
Стрелка -> означает “вернуть это значение”. Никаких break не нужно!
Можно группировать случаи через запятую:
String dayType = switch (dayNum) {
case 1, 2, 3, 4, 5 -> "Рабочий день";
case 6, 7 -> "Выходной";
default -> "Неверный день";
};
Если нужно выполнить несколько строк кода, используйте блок с yield:
int numLetters = switch (dayName) {
case "Понедельник", "Воскресенье" -> 11;
case "Среда" -> {
System.out.println("Середина недели!");
yield 5; // yield возвращает значение
}
default -> 0;
};
Цикл while
Цикл while повторяет код пока условие истинно. Условие проверяется перед каждой итерацией.
int count = 0;
while (count < 5) {
System.out.println("count: " + count);
count++;
}
Вывод:
count: 0
count: 1
count: 2
count: 3
count: 4
Если условие сразу ложно, код внутри цикла не выполнится ни разу:
int x = 10;
while (x < 5) {
System.out.println("Не выполнится");
}
Осторожно: Убедитесь что условие когда-нибудь станет ложным, иначе получится бесконечный цикл!
Цикл do-while
Цикл do-while похож на while, но проверяет условие после выполнения тела. Это означает, что код выполнится минимум один раз.
int number = 0;
do {
System.out.println("number: " + number);
number++;
} while (number < 3);
Вывод:
number: 0
number: 1
number: 2
Даже если условие изначально ложно, код выполнится один раз:
int x = 10;
do {
System.out.println("Выполнится хотя бы раз");
} while (x < 5); // условие ложно, но код уже выполнился
Вывод:
Выполнится хотя бы раз
Цикл for
Цикл for отлично подходит когда знаете сколько раз нужно повторить код.
Базовый for
for (int i = 0; i < 5; i++) {
System.out.println("i: " + i);
}
Вывод:
i: 0
i: 1
i: 2
i: 3
i: 4
Цикл for состоит из трёх частей:
- Инициализация (
int i = 0) - выполняется один раз в начале - Условие (
i < 5) - проверяется перед каждой итерацией - Обновление (
i++) - выполняется после каждой итерации
Все три части опциональны:
// Бесконечный цикл
for (;;) {
// будет работать вечно
}
Можно использовать несколько переменных:
for (int i = 0, j = 10; i < j; i++, j--) {
System.out.println("i=" + i + ", j=" + j);
}
Вывод:
i=0, j=10
i=1, j=9
i=2, j=8
i=3, j=7
i=4, j=6
Enhanced for (for-each)
Для обхода массивов и коллекций есть упрощённая форма:
int[] numbers = {10, 20, 30, 40, 50};
for (int num : numbers) {
System.out.println(num);
}
Вывод:
10
20
30
40
50
Читается как “для каждого num в numbers”. Это намного проще чем обычный for с индексами!
Работает с любыми коллекциями:
List<String> names = Arrays.asList("Анна", "Боб", "Карл");
for (String name : names) {
System.out.println("Привет, " + name);
}
Ограничение: Enhanced for не позволяет изменять элементы массива или получать индекс. Для этого используйте обычный
for.
Прерывание потока: break и continue
break - выход из цикла
break немедленно завершает цикл:
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // выход из цикла
}
System.out.println(i);
}
Вывод:
0
1
2
3
4
Полезно для поиска элемента:
int[] numbers = {5, 10, 15, 20, 25};
int target = 15;
boolean found = false;
for (int num : numbers) {
if (num == target) {
found = true;
break; // нашли, выходим
}
}
System.out.println("Найдено: " + found);
continue - пропуск итерации
continue пропускает оставшийся код в текущей итерации и переходит к следующей:
for (int i = 0; i < 5; i++) {
if (i == 2) {
continue; // пропускаем i=2
}
System.out.println(i);
}
Вывод:
0
1
3
4
Полезно для фильтрации:
int[] numbers = {-5, 10, -3, 15, -7, 20};
int sum = 0;
for (int num : numbers) {
if (num < 0) {
continue; // пропускаем отрицательные
}
sum += num;
}
System.out.println("Сумма положительных: " + sum); // 45
Метки для вложенных циклов
Когда нужно выйти из вложенного цикла, используйте метки:
outer: for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
break outer; // выход из ОБОИХ циклов
}
System.out.println("i=" + i + ", j=" + j);
}
}
Вывод:
i=0, j=0
i=0, j=1
i=0, j=2
i=1, j=0
Это работает и с continue:
outer: for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (j == 1) {
continue outer; // к следующей итерации внешнего цикла
}
System.out.println("i=" + i + ", j=" + j);
}
}
Оператор return
return завершает метод и возвращает управление:
public static int findMax(int a, int b) {
if (a > b) {
return a;
}
return b;
}
Можно использовать несколько return:
public static String getGrade(int score) {
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
if (score >= 60) return "D";
return "F";
}
Для void методов return просто выходит:
public static void printPositive(int num) {
if (num <= 0) {
return; // выход из метода
}
System.out.println("Положительное: " + num);
}
Обработка исключений
try-catch
Обрабатывайте ошибки чтобы программа не упала:
try {
int result = 10 / 0; // ошибка деления на ноль
} catch (ArithmeticException e) {
System.out.println("Ошибка: нельзя делить на ноль!");
}
Можно ловить несколько типов исключений:
try {
String text = null;
System.out.println(text.length());
} catch (NullPointerException e) {
System.out.println("Переменная null");
} catch (Exception e) {
System.out.println("Другая ошибка");
}
finally - код который выполнится всегда
Блок finally выполняется независимо от того, было ли исключение:
FileReader file = null;
try {
file = new FileReader("data.txt");
// читаем файл
} catch (IOException e) {
System.out.println("Ошибка чтения");
} finally {
if (file != null) {
try {
file.close(); // закрываем файл в любом случае
} catch (IOException e) {
e.printStackTrace();
}
}
}
try-with-resources
Java 7+ позволяет автоматически закрывать ресурсы:
try (FileReader file = new FileReader("data.txt")) {
// читаем файл
} catch (IOException e) {
System.out.println("Ошибка чтения");
}
// файл закроется автоматически!
Это работает с любыми AutoCloseable ресурсами.
Оператор assert
Проверяйте условия во время разработки:
int age = 15;
assert age >= 18 : "Возраст должен быть >= 18";
Важно: Assertions по умолчанию отключены! Включите их флагом
-eaпри запуске:java -ea Main
С сообщением:
double price = -10.0;
assert price > 0 : "Цена должна быть положительной, а не " + price;
Примечание: Не используйте assertions для проверки аргументов в публичных методах. Используйте исключения вместо этого.
Pattern Matching (Java 16+)
instanceof с паттернами
Старый способ:
Object obj = "Hello";
if (obj instanceof String) {
String s = (String) obj; // нужно приведение типа
System.out.println(s.toUpperCase());
}
Новый способ (Java 16+):
Object obj = "Hello";
if (obj instanceof String s) { // s уже String!
System.out.println(s.toUpperCase());
}
Switch с паттернами (Java 17+)
Object obj = 42;
String result = switch (obj) {
case Integer i -> "Число: " + i;
case String s -> "Строка: " + s;
case null -> "Это null";
default -> "Неизвестный тип";
};
С условиями (guards):
Object obj = 42;
String description = switch (obj) {
case Integer i when i > 0 -> "Положительное число";
case Integer i when i < 0 -> "Отрицательное число";
case Integer i -> "Ноль";
default -> "Не число";
};
Record Patterns (Java 21+)
record Point(int x, int y) {}
Point p = new Point(3, 4);
String location = switch (p) {
case Point(0, 0) -> "Начало координат";
case Point(int x, 0) -> "На оси X: " + x;
case Point(0, int y) -> "На оси Y: " + y;
case Point(int x, int y) -> "Точка (" + x + ", " + y + ")";
};
Полезные советы
Используйте фигурные скобки
Даже для однострочных блоков:
// Плохо
if (condition)
doSomething();
// Хорошо
if (condition) {
doSomething();
}
Это предотвращает ошибки при добавлении кода.
Извлекайте сложные условия
// Плохо
if (user.getAge() >= 18 && user.hasLicense() && !user.isBanned()) {
allowDriving();
}
// Хорошо
boolean canDrive = user.getAge() >= 18
&& user.hasLicense()
&& !user.isBanned();
if (canDrive) {
allowDriving();
}
Избегайте магических чисел
// Плохо
if (status == 1) {
// что означает 1?
}
// Хорошо
final int STATUS_ACTIVE = 1;
if (status == STATUS_ACTIVE) {
// ясно!
}
// Ещё лучше - enum
enum Status { ACTIVE, INACTIVE, PENDING }
if (status == Status.ACTIVE) {
// идеально!
}
Предпочитайте ранний выход
// Плохо
public void process(String data) {
if (data != null) {
if (data.length() > 0) {
// много кода
}
}
}
// Хорошо
public void process(String data) {
if (data == null || data.length() == 0) {
return; // ранний выход
}
// много кода
}
Итоги
Вы изучили основные управляющие конструкции Java:
if,else if,elseдля условного выполненияswitchдля множественного выбора (классический и современный)whileиdo-whileдля циклов с условиемforи enhanced for для итерацииbreak,continue,returnдля управления потокомtry-catch-finallyдля обработки ошибок- Pattern matching для современного Java
Теперь вы можете эффективно управлять потоком выполнения ваших программ!
Практика
Попробуйте написать программы для:
- Проверки является ли год високосным
- Вычисления факториала числа
- Поиска всех простых чисел до N
- Конвертации температуры между Цельсием и Фаренгейтом
Удачи в программировании!