Оператор switch
Вторым оператором выбора в C# является оператор switch, который обеспечивает многонаправленное ветвление программы. Следовательно, этот оператор позволяет сделать выбор среди нескольких альтернативных вариантов дальнейшего выполнения программы. Несмотря на то, что многонаправленная проверка может быть организована с помощью последовательного ряда вложенных операторов if, во многих случаях более эффективным оказывается применение оператора switch. Этот оператор действует следующим образом. Значение выражения последовательно сравнивается с константами выбора из заданного списка. Как только будет обнаружено совпадение с одним из условий выбора, выполняется последовательность операторов, связанных с этим условием. Ниже приведена общая форма оператора switch.
switch(выражение) {
case константа1:
последовательность операторов
break;
case константа2:
последовательность операторов
break;
case константа3:
последовательность операторов
break;
.
.
.
default:
последовательность операторов
break;
}
Заданное выражение в операторе switch должно быть целочисленного типа (char, byte, short или int), перечислимого или же строкового. (О перечислениях и символьных строках типа string речь пойдет далее в этом курсе.) А выражения других типов, например с плавающей точкой, в операторе switch не допускаются.
Зачастую выражение, управляющее оператором switch, просто сводится к одной переменной. Кроме того, константы выбора должны иметь тип, совместимый с типом выражения. В одном операторе switch не допускается наличие двух одинаковых по значению констант выбора.
Последовательность операторов из ветви default выполняется в том случае, если ни одна из констант выбора не совпадает с заданным выражением. Ветвь default не является обязательной. Если же она отсутствует и выражение не совпадает ни с одним из условий выбора, то никаких действий вообще не выполняется. Если же происходит совпадение с одним из условий выбора, то выполняются операторы, связанные с этим условием, вплоть до оператора break.
Ниже приведен пример программы, в котором демонстрируется применение оператора switch.
// Продемонстрировать применение оператора switch,
using System;
class SwitchDemo {
static void Main() {
int i;
for (i = 0; i < 10; i++)
switch (i) {
case 0:
Console.WriteLine("i равно нулю");
break;
case 1:
Console.WriteLine("i равно единице");
break;
case 2:
Console.WriteLine("i равно двум");
break;
case 3:
Console.WriteLine("i равно трем");
break;
case 4:
Console.WriteLine("i равно четырем");
break;
default:
Console.WriteLine("i равно или больше пяти");
break;
}
}
}
Результат выполнения этой программы выглядит следующим образом.
i равно нулю
i равно единице
i равно двум
i равно трем
i равно четырем
i равно или больше пяти
i равно или больше пяти
i равно или больше пяти
i равно или больше пяти
i равно или больше пяти
Как видите, на каждом шаге цикла выполняются операторы, связанные с совпадающей константой выбора, в обход всех остальных операторов. Когда же значение переменной i становится равным или больше пяти, то оно не совпадает ни с одной из констант выбора, а следовательно, выполняются операторы из ветви default.
В приведенном выше примере оператором switch управляла переменная i типа int. Как пояснялось ранее, для управления оператором switch может быть использовано выражение любого целочисленного типа, включая и char. Ниже приведен пример применения выражения и констант выбора типа char в операторе switch.
// Использовать элементы типа char для управления оператором switch,
using System;
class SwitchDemo2 {
static void Main() {
char ch;
for (ch = 'A'; ch <= 'E'; ch++)
switch (ch) {
case 'A':
Console.WriteLine("ch содержит A");
break;
case 'B':
Console.WriteLine("ch содержит B");
break;
case 'C':
Console.WriteLine("ch содержит C");
break;
case 'D':
Console.WriteLine("ch содержит D");
break;
case 'E':
Console.WriteLine("ch содержит E");
break;
}
}
}
Вот какой результат дает выполнение этой программы.
ch содержит A
ch содержит B
ch содержит C
ch содержит D
ch содержит E
Обратите в данном примере внимание на отсутствие ветви default в операторе switch. Напомним, что ветвь default не является обязательной. Когда она не нужна, ее можно просто опустить.
Переход последовательности операторов, связанных с одной ветвью case, в следующую ветвь case считается ошибкой, поскольку в C# должно непременно соблюдаться правило недопущения «провалов» в передаче управления ходом выполнения программы. Именно поэтому последовательность операторов в каждой ветви case оператора switch оканчивается оператором break. (Избежать подобных «провалов», можно также с помощью оператора безусловного перехода goto, рассматриваемого далее в этой главе, но для данной цели чаще применяется оператор break.)
Когда в последовательности операторов отдельной ветви case встречается оператор break, происходит выход не только из этой ветви, но из всего оператора switch, а выполнение программы возобновляется со следующего оператора, находящегося за пределами оператора switch. Последовательность операторов в ветви default также должна быть лишена “провалов”, поэтому она завершается, как правило, оператором break.
Правило недопущения «провалов» относится к тем особенностям языка C#, которыми он отличается от C, C++ и Java. В этих языках программирования одна ветвь case может переходить (т. е. «проваливаться») в другую. Данное правило установлено в C# для ветвей case по двум причинам.
Во-первых, оно дает компилятору возможность свободно изменять порядок следования последовательностей операторов из ветвей case для целей оптимизации. Такая реорганизация была бы невозможной, если бы одна ветвь case могла переходить в другую.
И во-вторых, требование завершать каждую ветвь case явным образом исключает непроизвольные ошибки программирования, допускающие переход одной ветви case в другую.
Несмотря на то, что правило недопущения «провалов» не допускает переход одной ветви case в другую, в двух или более ветвях case все же разрешается ссылаться с помощью меток на одну и ту же кодовую последовательность, как показано в следующем примере программы.
// Пример "проваливания" пустых ветвей case.
using System;
class EmptyCasesCanFall {
static void Main() {
int i;
for (i = 1; i < 5; i++)
switch (i) {
case 1:
case 2:
case 3:
Console.WriteLine("i равно 1, 2 или 3");
break;
case 4:
Console.WriteLine("i равно 4");
break;
}
}
}
Ниже приведен результат выполнения этой программы.
i равно 1, 2 или 3
i равно 1, 2 или 3
i равно 1, 2 или 3
i равно 4
Если значение переменной i в данном примере равно 1, 2 или 3, то выполняется первый оператор, содержащий вызов метода WriteLine(). Такое расположение нескольких меток ветвей case подряд не нарушает правило недопущения «провалов»; поскольку во всех этих ветвях используется одна и та же последовательность операторов.
Расположение нескольких меток ветвей case подряд зачастую применяется в том случае, если у нескольких ветвей имеется общий код. Благодаря этому исключается излишнее дублирование кодовых последовательностей.
The C++ Switch case statement evaluates a given expression and based on the evaluated value(matching a certain condition), it executes the statements associated with it. It is an alternative to the long if-else-if ladder which provides an easy way to dispatch execution to different parts of code based on the value of the expression.
What is a switch statement in C++?
The switch statement in C++ is a flow control statement that is used to execute the different blocks of statements based on the value of the given expression. We can create different cases for different values of the switch expression. We can specify any number of cases in the switch statement but the case value can only be of type int or char.
Syntax of switch Statement in C++
switch (expression) {
case value_1:
// statements_1;
break;
case value_2:
// statements_2;
break;
.....
.....
default:
// default_statements;
break;
}
The following example demonstrate how to use the switch statement syntax in C++.
Example: C Program to demonstrate the syntax of switch in C++
C++
#include <iostream>
using namespace std;
int main()
{
char x = 'A';
switch (x) {
case 'A':
cout << "Choise is A";
break;
case 'B':
cout << "Choise is B";
break;
case 'C':
cout << "Choise is C";
break;
default:
cout << "Choice other than A, B and C";
break;
}
return 0;
}
Rules of the switch case statement in C++
There are some rules that we need to follow when using switch statements in C++. They are as follows:
- The case value must be either int or char type.
- There can be any number of cases.
- No duplicate case values are allowed.
- Each statement of the case can have a break statement. It is optional.
- The default Statement is also optional.
Flowchart of Switch Statement in C++
Flowchart of the switch statement in C++
Working of switch Statement in C++
The working of the switch statement in C is as follows:
- Step 1: The switch expression is evaluated.
- Step 2: The evaluated value is then matched against the present case values.
- Step 3A: If the matching case value is found, that case block is executed.
- Step 3B: If the matching code is not found, then the default case block is executed if present.
- Step 4A: If the break keyword is present in the block, then program control comes out of the switch statement.
- Step 4B: If the break keyword is not present, then all the cases after the matching case are executed.
- Step 5: Statements after the switch statement is executed.
Important Points About Switch Case Statements
1. Switch expression should result in a constant value
If the expression provided in the switch statement does not result in a constant value, it would not be valid. Some valid expressions for switch case will be,
// Constant expressions allowed switch(1+2+23) switch(1*2+3%4) // Variable expression are allowed provided // they are assigned with fixed values switch(a*b+c*d) switch(a+b+c)
2. Expression must evaluate only int or char type values.
The switch statement can only evaluate the integer or character value. So the switch expression should return the values of type int or char only.
3. Break in switch case
The break keyword is used in the switch case to break out of the switch when encountered. It is used at the end of every case block so that when the matching case is executed, the program control comes out of the loop.
The break statement is optional. If omitted, all the cases after the matching case will also be executed.
Example of switch cases without the break statement
C
#include <stdio.h>
int main()
{
int var = 2;
switch (var) {
case 1:
printf("Case 1 is executed.\n");
case 2:
printf("Case 2 is executed.\n");
case 3:
printf("Case 3 is executed.\n");
case 4:
printf("Case 4 is executed.");
}
return 0;
}
Output
Case 2 is executed. Case 3 is executed. Case 4 is executed.
4. Default in switch case
The default keyword is used to define a default case which will be executed when no case value is matched. It is also an optional statement and the switch case statement runs without problem if it is omitted.
5. No duplicate Case Values
In the C switch statement, duplicate case values are not allowed. All the case values must be unique.
6. Nested Switch Statements
In C++, we can nest one switch inside another switch statement without any problem. Though it is avoided most of the time as it makes the program more complex and unreadable.
Example of Nested Switch Statement
C++
#include <iostream>
using namespace std;
int main()
{
int var1 = 1;
int var2 = 0;
switch (var1) {
case 0:
cout << "Outer Switch Case 0\n";
break;
case 1:
cout << "Outer Switch Case 1\n";
switch (var2) {
case 0:
cout << "Inner Switch Case 0\n";
break;
}
break;
default:
cout << "Default Case of Outer Loop";
break;
}
return 0;
}
Output
Outer Switch Case 1 Inner Switch Case 0
Examples of switch Statement in C++
Example 1: C++ Program to make a Simple Calculator using the switch
C++
#include <iostream>
#include <stdlib.h>
using namespace std;
int main()
{
char choice;
int x, y;
while (1) {
cout << "Enter the Operator (+,-,*,/)\nEnter x to "
"exit\n";
cin >> choice;
if (choice == 'x') {
exit(0);
}
cout << "Enter the two numbers: ";
cin >> x >> y;
switch (choice) {
case '+':
cout << x << " + " << y << " = " << x + y
<< endl;
break;
case '-':
cout << x << " - " << y << " = " << x - y
<< endl;
break;
case '*':
cout << x << " * " << y << " = " << x * y
<< endl;
break;
case '/':
cout << x << " / " << y << " = " << x / y
<< endl;
break;
default:
printf("Invalid Operator Input\n");
}
}
return 0;
}
Output
Enter the operator (+, -, *, /) Enter x to exit + Enter the two numbers: 100 + 200 100 + 200 = 300
Example 2: C++ Program to print the day using switch case
C++
#include <iostream>
using namespace std;
class Day {
private:
int day = 3;
public:
void set_data()
{
cout << "Enter no of day you want to display: ";
cin >> day;
}
void display_day()
{
switch (day) {
case 1:
cout << "MONDAY";
break;
case 2:
cout << "TUESDAY";
break;
case 3:
cout << "WEDNESDAY";
break;
case 4:
cout << "THURSDAY";
break;
case 5:
cout << "FRIDAY";
break;
case 6:
cout << "SATURDAY";
break;
case 7:
cout << "SUNDAY";
break;
default:
cout << "INVALID INPUT";
break;
}
}
};
main()
{
Day d1;
d1.display_day();
return 0;
}
Advantages of switch Statement in C++
- Easier to debug and maintain for a large number of conditions.
- Faster execution speed.
- Easier to read than if else if.
Disadvantages of switch Statement in C++
- Switch case can only evaluate int or char type.
- No support for logical expressions.
- Have to keep in mind to add a break in every case.
Conclusion
In this article, we discussed the switch statement and how we can use it to replace long and complex if else if ladder statements. We also discussed the cases in which we can use the switch statement to make our C++ program more efficient.
FAQs on C++ Switch Statement
1. Define switch in C++.
The switch in C is a decision-making statement that can execute the different blocks of code based on the value of the expression specified.
2. Which statements are optional in the C++ switch?
The default and break are optional in the switch in C++.
3. What are the differences between switch and if else if ladder in C?
Following are the main differences between switch and if else if ladder in C++:
|
switch |
if else if |
|---|---|
| It executes the different cases on the basis of the value of the switch variable. | It executes the different blocks based on the condition specified. |
| It can only evaluate the int or char type expressions. | It can evaluate any type of expression. |
| Faster and easier to read for a large number of conditions. | It can get messy when there are lots of conditions. |
Whether you’re preparing for your first job interview or aiming to upskill in this ever-evolving tech landscape, GeeksforGeeks Courses are your key to success. We provide top-quality content at affordable prices, all geared towards accelerating your growth in a time-bound manner. Join the millions we’ve already empowered, and we’re here to do the same for you. Don’t miss out — check it out now!
Last Updated :
20 Mar, 2023
Like Article
Save Article
Содержание
- 1. Какое назначение в программах имеет оператор выбора switch?
- 2. Какая общая форма оператора выбора switch?
- 3. Принцип работы оператора switch
- 4. Пример использования оператора выбора switch, в котором есть блок default
- 5. Пример использования оператора switch, в котором пропущены операторы break
- 6. Пример использования оператора switch, в котором отсутствует блок default
- 7. Вложенные инструкции switch
- 8. Преимущества оператора switch в сравнении с оператором if
- 9. Недостатки оператора switch в сравнении с оператором if
- Связанные темы
Поиск на других ресурсах:
1. Какое назначение в программах имеет оператор выбора switch?
Оператор выбора switch есть близок к оператору условного перехода if — else. Он позволяет организовать разветвление процесса выполнения в программе. В некоторых случаях использование оператора выбора switch дает более компактный программный код в сравнении с оператором условного перехода if — else.
⇑
2. Какая общая форма оператора выбора switch?
В C-ориентированных языках программирования, к которым принадлежит и Java, оператор выбора switch имеет одинаковую общую форму представления:
switch (выражение) { case значение1: // операторы ... break; case значение2: // операторы ... break; ... case значениеN: // операторы ... break; default: // операторы, которые выполняются по умолчанию ... }
где
- выражение – управляющее выражение, которое может иметь один из целочисленных типов: byte, int, short, char или перечислительный тип. В новых версиях JDK выражение может иметь тип String.
- значение1, значение2, …, значениеN – константные выражения (литеральные значения). Каждое значение должно быть совместимым по типу с указанным выражением.
Блок default может отсутствовать.
⇑
3. Принцип работы оператора switch
Оператор выбора switch работает по следующему принципу. Значение выражения сравнивается с любым из значений (значение1, значение2, …, значениеN), которые следуют после ключевого слова case. Если найдено совпадение, тогда выполняются операторы, которые следуют после этой части case. Если ни в одном из вариантов после case совпадение не найдено, тогда выполняются операторы, которые идут в блоке default. Если блок default отсутствует (блок может отсутствовать), тогда ничего не происходит и выполняется следующий оператор, который следует за оператором switch.
Оператор break необходим для немедленного выхода из оператора switch. Если выполняется оператор break, тогда выполняется следующий после switch оператор.
⇑
4. Пример использования оператора выбора switch, в котором есть блок default
По заданному значению n = 1..7 вывести название соответствующего дня недели. Учесть возможные ошибочные значения n.
// оператор выбора switch int n; String s; // ввод значения n n = 6; switch (n) { case 1: s = "Понедельник"; break; case 2: s = "Вторник"; break; case 3: s = "Среда"; break; case 4: s = "Четверг"; break; case 5: s = "Пятница"; break; case 6: s = "Суббота"; break; case 7: s = "Воскресенье"; break; default: s = "Неправильно введен день"; } System.out.println(s);
⇑
5. Пример использования оператора switch, в котором пропущены операторы break
Как известно, оператор break, может отсутствовать.
В данном примере по введенному значению номера дня недели n = 1..7 определяется выходной этот день или рабочий.
// оператор выбора switch int n; String s; // ввод значения n n = 3; switch (n) { case 1: case 2: case 3: case 4: case 5: s = "Рабочий день"; break; case 6: case 7: s = "Выходной день"; break; default: s = "Неправильно введен день"; } System.out.println(s);
⇑
6. Пример использования оператора switch, в котором отсутствует блок default
Дано целое число n = 1..3. По данному значению переменной n определить:
- длину окружности;
- площадь круга;
- объем шара.
// оператор выбора switch int n; double pi = 3.1415; double r; // ввод значений n, r // ... switch (n) { case 1: double d = 2*pi*r; System.out.println("Длина окружности = " + d); break; case 2: double s = pi*r*r; System.out.println("Площадь круга = " + s); break; case 3: double v = 4.0/3.0 * pi*r*r*r; System.out.println("Объем шара = " + v); break; }
⇑
7. Вложенные инструкции switch. Пример
Инструкция switch может содержать другую, вложенную инструкцию switch. В каждом операторе switch определяется свой блок кода. Блоки кода есть независимыми друг от друга.
Пример. В данном примере на основании номера года (year) и месяца (month) вычисляется количество дней в месяце (переменная days). Задача решена с использованием вложенных операторов if и switch
// вычисление количества дней в месяце года int f; int year; int month; int days; // заданы месяц и год year = 2404; month = 2; switch(month) { case 4: case 6: case 9: case 11: days = 30; break; case 2: // месяц февраль f = 0; // вложенный оператор if if (year%400==0) f=1; else if (year%100==0) f=0; else if (year%4==0) f=1; // вложенный оператор switch switch (f) { case 0: days=28; break; default: days=29; } break; default: days = 31; } System.out.println("Days = " + days);
⇑
8. Преимущества оператора switch в сравнении с оператором if
В сравнении с оператором if преимущества оператора switch следующие:
- при выборе из большой группы значений оператор switch работает более быстрее чем оператор if-else. Это связано с тем, что в операторе switch константы всех ветвей case и выражение имеют одинаковый тип. Поэтому достаточно осуществить проверку на равенство. В операторе if-else компилятор заведомо не знает типы результатов выражений в разных ветвях сравнения, что требует дополнительных преобразований занимающих время.
- в случаях когда выполняется код для нескольких операторов ветвей case без указания разделяющих их операторов break.
⇑
9. Недостатки оператора switch в сравнении с оператором if
В сравнении с оператором if, оператор switch имеет следующие недостатки:
- в операторе switch, в каждой ветви выполняется проверка только на равенство (поиск на совпадение в ветви case). В операторе if в каждой ветви можно вычислять любое логическое выражение любого типа;
- в операторе switch выражение, которое сравнивается может быть только типа int, перечислением enum или типа String (начиная с версии JDK 7). Выражение не может быть типом с плавающей запятой. В операторе if сравниваемое выражение может быть любого типа;
- в двух разных ветвях case константы не могут иметь одинаковые значения. Одинаковые значения в ветвях case допускаются, только когда эти ветви размещаются на разных уровнях вложения в случае вложенных операторов switch.
⇑
Связанные темы
- Оператор условного перехода if
- Конструкция if-else-if
- Операции. Операция присваивания. Тернарная операция ? : . Приоритет операций
- C++. Оператор выбора switch
⇑
В этом руководстве вы научитесь использовать оператор switch в Java для управления потоком выполнения вашей программы.
В Java мы использовали if..else..if для выполнения блока кода среди нескольких блоков. Однако синтаксис if … else … if слишком длинный.
Следовательно, мы можем использовать оператор switch вместо длинных if … else … if. Использование операторов switch делает наш код более читабельным.
Switch эволюционировал со временем – были добавлены новые поддерживаемые типы, особенно в Java 5 и 7. Кроме того, он продолжает развиваться – выражения switch, вероятно, будут введены в Java 12.
switch (variable/expression) {
case value1:
// statements of case1
break;
case value2:
// statements of case2
break;
.. .. ...
.. .. ...
default:
// default statements
}
Оператор switch оценивает выражение (в основном переменное) и сравнивает его со значениями (могут быть выражениями) каждой метки case.
Теперь, если значение соответствует определенной метке case, то выполняются все операторы соответствующей метки case.
Например, если переменная / выражение равно значению 2. В этом случае выполняются все операторы этого совпадающего case (операторы case2).
Обратите внимание, что в каждом случае используются операторы break. Оператор break используется для завершения выполнения оператора switch.
Это важно, потому что если break не используется, все операторы после соответствующего case выполняются последовательно до конца оператора switch.
Что следует помнить:
- Для выражения переключения может быть одно или N значений case.
- Значение case должно быть только типа выражения переключения. Значение case должно быть буквальным или постоянным. Он не допускает переменных.
- Значения case должны быть уникальными. В случае повторяющегося значения он отображает ошибку времени компиляции.
- Выражение переключателя Java должно быть byte, short, int, long, перечислением и строкой.
- Каждый оператор case может иметь оператор break, который не является обязательным.
- Когда элемент управления достигает оператора break, он переходит к элементу управления после выражения switch.
- Также case может иметь метку по умолчанию, которая является необязательной.
Пример 1
class Main {
public static void main(String[] args) {
int week = 4;
String day;
// switch statement to check day
switch (week) {
case 1:
day = "Sunday";
break;
case 2:
day = "Monday";
break;
case 3:
day = "Tuesday";
break;
// match the value of week
case 4:
day = "Wednesday";
break;
case 5:
day = "Thursday";
break;
case 6:
day = "Friday";
break;
case 7:
day = "Saturday";
break;
default:
day = "Invalid day";
break;
}
System.out.println("The day is " + day);
}
}
Вывод:
The day is Wednesday
В приведенном выше примере мы использовали оператор switch, чтобы узнать день недели. Здесь у нас есть переменная неделя, которая содержит целочисленное значение. Значение сравнивается с каждым случаем внутри блока переключателя.
Здесь значение недели равно 4. Следовательно, оно соответствует случаю 4. Таким образом, выполняется оператор внутри случая 4.
Пример 2: создание калькулятора с использованием оператора switch
Программа ниже принимает от пользователя три ввода: один оператор и 2 числа. На основе оператора, предоставленного пользователем, он выполняет расчет чисел. Затем результат отображается на экране.
Перед тем как приступить к работе с программой, убедитесь, что вы знаете о Java Scanner, чтобы принимать данные от пользователя.
import java.util.Scanner;
class Main {
public static void main(String[] args) {
char operator;
Double number1, number2, result;
// create an object of Scanner class
Scanner scanner = new Scanner(System.in);
System.out.print("Enter operator (either +, -, * or /): ");
// ask user to enter operator
operator = scanner.next().charAt(0);
System.out.print("Enter number1 and number2 respectively: ");
// ask user to enter numbers
number1 = scanner.nextDouble();
number2 = scanner.nextDouble();
switch (operator) {
// performs addition between numbers
case '+':
result = number1 + number2;
System.out.print(number1 + "+" + number2 + " = " + result);
break;
// performs subtraction between numbers
case '-':
result = number1 - number2;
System.out.print(number1 + "-" + number2 + " = " + result);
break;
// performs multiplication between numbers
case '*':
result = number1 * number2;
System.out.print(number1 + "*" + number2 + " = " + result);
break;
// performs division between numbers
case '/':
result = number1 / number2;
System.out.print(number1 + "/" + number2 + " = " + result);
break;
default:
System.out.println("Invalid operator!");
break;
}
}
}
Вывод:
Enter operator (either +, -, * or /): *
Enter number1 and number2 respectively: 1.4
-5.3
1.4*-5.3 = -7.419999999999999
В приведенном выше примере мы использовали оператор switch для создания калькулятора на Java. Он выполняет расчет на основе оператора, предоставленного пользователем.
Пример 3
Допустим, у нас есть следующие вложенные операторы if-else:
public String exampleOfIF(String animal) {
String result;
if (animal.equals("DOG") || animal.equals("CAT")) {
result = "domestic animal";
} else if (animal.equals("TIGER")) {
result = "wild animal";
} else {
result = "unknown animal";
}
return result;
}
Приведенный выше код выглядит не очень хорошо, и его будет сложно поддерживать и обдумывать. Чтобы улучшить читаемость, мы могли бы использовать здесь оператор switch:
public String exampleOfSwitch(String animal) {
String result;
switch (animal) {
case "DOG":
result = "domestic animal";
break;
case "CAT":
result = "domestic animal";
break;
case "TIGER":
result = "wild animal";
break;
default:
result = "unknown animal";
break;
}
return result;
}
Как показано выше, мы сравниваем animal аргумента switch с несколькими значениями case. Если ни одно из значений case не равно аргументу, то выполняется блок под меткой по умолчанию.
Проще говоря, оператор break используется для выхода из оператора switch.
Заявление break
Хотя большинство операторов switch в реальной жизни подразумевают, что должен быть выполнен только один из блоков case, оператор break необходим для выхода из switch после завершения блока.
Чтобы продемонстрировать это, опустим операторы break и добавим вывод в консоль для каждого блока:
public String forgetBreakInSwitch(String animal) {
switch (animal) {
case "DOG":
System.out.println("domestic animal");
default:
System.out.println("unknown animal");
}
}
Давайте выполним этот код ForgotBreakInSwitch(«DOG») и проверим вывод, чтобы убедиться, что все блоки выполняются:
domestic animal
unknown animal
Поэтому нам следует быть осторожными и добавлять операторы break в конце каждого блока, если нет необходимости переходить к коду под следующей меткой.
Единственный блок, в котором не требуется разрыв, – это последний, но добавление разрыва к последнему блоку делает код менее подверженным ошибкам.
Мы также можем воспользоваться этим поведением, чтобы опустить break, когда мы хотим, чтобы один и тот же код выполнялся для нескольких операторов case. Давайте перепишем пример из предыдущего раздела, сгруппировав первые 2 случая:
public String exampleOfSwitch(String animal) {
String result;
switch (animal) {
case "DOG":
case "CAT":
result = "domestic animal";
break;
case "TIGER":
result = "wild animal";
break;
default:
result = "unknown animal";
break;
}
return result;
}
Переключение Аргумента и регистра значений
Теперь давайте обсудим допустимые типы аргументов switch и значений case, требования к ним и то, как оператор switch работает со строками.
Типы данных
Мы не можем сравнивать все типы объектов и примитивов в операторе switch. Переключатель работает только с четырьмя примитивами и их оболочками, а также с типом перечисления и классом String:
- byte and Byte;
- short and Short;
- int and Integer;
- char and Character;
- enum;
- String.
Тип String доступен в операторе switch, начиная с Java 7. Тип перечисления был представлен в Java 5 и с тех пор доступен в операторе switch. Классы-оболочки также доступны с Java 5.
Конечно, аргументы переключателя и значения case должны быть одного типа.
Нет null значений
Мы не можем передать нулевое значение в качестве аргумента инструкции switch. Если мы это сделаем, программа выдаст исключение NullPointerException, используя наш первый пример переключения:
@Test(expected=NullPointerException.class)
public void whenSwitchAgumentIsNull_thenNullPointerException() {
String animal = null;
Assert.assertEquals("domestic animal", s.exampleOfSwitch(animal));
}
Конечно, мы также не можем передать null как значение в метку case оператора switch. Если мы это сделаем, код не скомпилируется.
Значения регистра как константы времени компиляции
Если мы попытаемся заменить значение case DOG переменной dog, код не будет компилироваться, пока мы не отметим переменную dog как final:
final String dog="DOG";
String cat="CAT";
switch (animal) {
case dog: //compiles
result = "domestic animal";
case cat: //does not compile
result = "feline"
}
Сравнение строк
Если оператор switch использовал оператор равенства для сравнения строк, мы не смогли бы правильно сравнить аргумент String, созданный с помощью оператора new, со значением case String.
К счастью, оператор switch использует скрытый метод equals().
@Test
public void whenCompareStrings_thenByEqual() {
String animal = new String("DOG");
assertEquals("domestic animal", s.exampleOfSwitch(animal));
}
switch выражения
JDK 13 теперь доступен и содержит улучшенную версию новой функции, впервые представленной в JDK 12: выражения switch.
Чтобы включить его, нам нужно передать компилятору параметр –enable-preview.
Новое выражение переключателя
Давайте посмотрим, как выглядит новое выражение переключения при переключении через несколько месяцев:
var result = switch(month) {
case JANUARY, JUNE, JULY -> 3;
case FEBRUARY, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER -> 1;
case MARCH, MAY, APRIL, AUGUST -> 2;
default -> 0;
};
Отправка значения типа Month.JUNE установит результат в 3.
Обратите внимание, что новый синтаксис использует оператор -> вместо двоеточия, которое мы использовали с операторами switch. Кроме того, нет ключевого слова break: выражение switch не пропускает регистры. Еще одним дополнением является тот факт, что теперь у нас могут быть критерии, разделенные запятыми.
Ключевое слово yield
Если пойти немного дальше, то есть возможность получить детальный контроль над тем, что происходит в правой части выражения, с помощью блоков кода. В таком случае нам нужно использовать ключевое слово yield:
var result = switch (month) {
case JANUARY, JUNE, JULY -> 3;
case FEBRUARY, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER -> 1;
case MARCH, MAY, APRIL, AUGUST -> {
int monthLength = month.toString().length();
yield monthLength * 4;
}
default -> 0;
};
Возврат внутри выражения переключателя
Вследствие различия между операторами switch и выражениями переключения, можно вернуться изнутри оператора, но нам не разрешено делать это из выражения.
Следующий пример вполне допустим и будет компилироваться:
switch (month) {
case JANUARY, JUNE, JULY -> { return 3; }
default -> { return 0; }
}
Однако следующий код не будет компилироваться, поскольку мы пытаемся выполнить возврат за пределами включающего выражения переключателя:
var result = switch (month) {
case JANUARY, JUNE, JULY -> { return 3; }
default -> { return 0; }
};
При использовании операторов switch на самом деле не имеет значения, охвачены ли все случаи.
Следующий код, например, имеет место быть:
switch (month) {
case JANUARY, JUNE, JULY -> 3;
case FEBRUARY, SEPTEMBER -> 1;
}
Однако для выражений переключения компилятор настаивает на том, чтобы были учтены все возможные случаи. Следующий фрагмент кода, например, не будет компилироваться, так как нет случая по умолчанию и не все возможные случаи рассмотрены:
var result = switch (month) {
case JANUARY, JUNE, JULY -> 3;
case FEBRUARY, SEPTEMBER -> 1;
}
Выражение switch, однако, будет действительным, когда будут рассмотрены все возможные случаи, как в следующем примере:
var result = switch (month) {
case JANUARY, JUNE, JULY -> 3;
case FEBRUARY, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER -> 1;
case MARCH, MAY, APRIL, AUGUST -> 2;
}
Обратите внимание, что в приведенном выше фрагменте кода нет регистра по умолчанию. Пока рассмотрены все случаи, выражение switch будет действительным.
Последнее обновление: 20.02.2023
Конструкция switch-case позволяет сравнить некоторое выражение с набором значений. Она имеет следующую форму:
switch(выражение)
{
case значение_1: инструкции_1;
case значение_2: инструкции_2;
...................
case значение_N: инструкции_N;
default: инструкции;
}
После ключевого слова switch в скобках идет сравниваемое выражение. Значение этого выражения последовательно
сравнивается со значениями после оператора сase. И если совпадение будет найдено, то будет выполняться
определенный блок сase.
Стоит отметить, что сравниваемое выражение в switch должно представлять один из целочисленных или символьных типов или перечисление (рассматриваются далее).
В конце конструкции switch может стоять блок default. Он необязателен и выполняется в том случае, если значение после switch не соответствует ни одному из операторов case. Например:
#include <iostream>
int main()
{
int x {2};
switch(x)
{
case 1:
std::cout << "x = 1" << "\n";
break;
case 2:
std::cout << "x = 2" << "\n";
break;
case 3:
std::cout << "x = 3" << "\n";
break;
default:
std::cout << "x is undefined" << "\n";
break;
}
}
Чтобы избежать выполнения последующих блоков case/default, в конце каждого блока ставится оператор break. То есть
в данном случае будет выполняться оператор
case 2: std::cout << "x = 2" << "\n"; break;
После выполнения оператора break произойдет выход из конструкции switch..case, и остальные операторы case будут проигнорированы.
Поэтому на консоль будет выведена следующая строка
x = 2
Стоит отметить важность использования оператора break. Если мы его не укажем в блоке case, то после этого блока выполнение перейдет к следующему блоку case. Например,
уберем из предыдущего примера все операторы break:
#include <iostream>
int main()
{
int x {2};
switch(x)
{
case 1:
std::cout << "x = 1" << "\n";
case 2:
std::cout << "x = 2" << "\n";
case 3:
std::cout << "x = 3" << "\n";
default:
std::cout << "x is undefined" << "\n";
}
}
В этом случае опять же будет выполняться оператор case 2:, так как переменная x=2. Однако так как этот блок case не завершается
оператором break, то после его завершения будет выполняться набор инструкций после case 3: даже несмотря на то, что переменная x по прежнему равна 2.
В итоге мы получим следующий консольный вывод:
x = 2 x = 3 x is undefined
Совмещение условий
Можно определять для нескольких меток case один набор инструкций:
#include <iostream>
int main()
{
int x {2};
switch(x)
{
case 1:
case 2:
std::cout << "x = 1 or 2" << "\n";
break;
case 3:
case 4:
std::cout << "x = 3 or 5" << "\n";
break;
case 5:
std::cout << "x = 5" << "\n";
break;
}
}
Здесь если x=1 или x=2, то выполняется одна и та же инструкция std::cout << "x = 1 or 2" << "\n". Аналогично для вариантов
x=3 и x=4 также определена общая инструкция.
Переменные в блоках case
Определение переменных в блоках case, возможно, встречается нечасто. Однако может вызвать затруднения. Так, если переменная определяется в блоке case, то все инструкции
блока помещаются в фигурные скобки (для блока default это не требуется):
#include <iostream>
int main()
{
int x {2};
switch(x)
{
case 1:
{
int a{10};
std::cout << a << std::endl;
break;
}
case 2:
{
int b{20};
std::cout << b << std::endl;
break;
}
default:
int c{30};
std::cout << c << std::endl;
}
}
Блок switch с инициализацией переменной
Иногда в конструкции switch для различных промежуточных вычислений необходимо определить переменную. Для этой цели начиная со стандарта
C++17 язык С++ поддерживает особую форму конструкции switch:
switch(инициализация; выражение)
{
// ..........
}
Подобная форма также принимает выражение, значение которого сравнивается с константами после операторов case. Но теперь перед выражением еще может идти
определение и инициализация переменной. Например:
#include <iostream>
int main()
{
char op = '+';
int n = 10;
switch(int k{2}; op)
{
case '+':
std::cout << n + k << std::endl;
break;
case '-':
std::cout << n - k << std::endl;
break;
case '*':
std::cout << n * k << std::endl;
break;
}
}
В данном случае в конструкции switch определяется переменная k, которая доступна только в рамках этой конструкции switch. В качестве выражения
используется значение переменной op, которая представляет знак операции. И в зависимости от этого значения, выполняем определенную операцию с переменными n и k.
Уровень сложности
Средний
Время на прочтение
11 мин
Количество просмотров 62K
Старый добрый switch был в Java с первого дня. Мы все используем его и привыкли к нему — особенно к его причудам (кого-нибудь еще раздражает break?). Но начиная с Java 12, ситуация начала меняться: switch вместо оператора стал выражением:
boolean result = switch (ternaryBool) {
case TRUE -> true;
case FALSE -> false;
case FILE_NOT_FOUND -> throw new UncheckedIOException(
"This is ridiculous!",
new FileNotFoundException());
default -> throw new IllegalArgumentException("Seriously?!");
};
Результат работы switch-выражения теперь можно сохранять в переменную; ушла необходимость использовать break в каждой ветке case благодаря лямбда-синтаксису и многое другое.
Когда дело доходит до switch после Java 14, необходимо выбрать стиль его использования:
- оператор или выражение (с Java 14)
- двоеточия или стрелки (с Java 14)
- метки или шаблоны (3-й превью в Java 19)
В этом руководстве я расскажу обо всем, что необходимо знать о switch-выражениях, и как их лучше всего использовать в современной Java.
Недостатки оператора switch
Прежде, чем перейти к обзору нововведений, давайте рассмотрим один пример кода. Допустим, мы столкнулись с «ужасным» тернарным boolean и хотим преобразовать его в обычный boolean. Вот один из способов сделать это:
// Тернарный boolean
enum Bool {
TRUE,
FALSE,
FILE_NOT_FOUND
};
boolean result;
switch (ternaryBool) {
case TRUE:
result = true;
break;
case FALSE:
result = false;
break;
case FILE_NOT_FOUND:
// объявление переменной для демонстрации проблемы в default
var ex = new UncheckedIOException("This is ridiculous!",
new FileNotFoundException());
throw ex;
default:
// А вот и проблема: мы не можем объявить еще одну переменную с именем ex
var ex2 = new IllegalArgumentException("Seriously?!");
throw ex2;
}
Реализация данного кода хромает:
- наличие break в каждой ветке, которые легко забыть
- можно не учесть все возможные значения ternaryBool (забыть реализовать какой-то case)
- с переменной result не все гладко — область видимости не соответствует ее использованию
- нельзя объявить в разных ветках переменные с одинаковым именем
Согласитесь, что данное решение выглядит крайне громоздко и неудобно — тут явно есть, что улучшить.
А вот пример попроще, демонстрирующий похожие проблемы:
int result;
switch (number) {
case 1:
result = callMethod("one");
break;
case 2:
result = callMethod("two");
break;
default:
result = callMethod("many");
break;
}
Давайте попробуем устранить все недостатки, поместив switch в отдельный метод:
private static boolean toBoolean(Bool ternaryBool) {
switch (ternaryBool) {
case TRUE: return true;
case FALSE: return false;
case FILE_NOT_FOUND:
throw new UncheckedIOException("This is ridiculous!",
new FileNotFoundException());
// без default метод не скомпилируется
default:
throw new IllegalArgumentException("Seriously?!");
}
}
Так намного лучше: отсутствует фиктивная переменная result, нет break, загромождающих код и сообщений компилятора об отсутствии default (даже если в этом нет необходимости, как в данном случае).
Но если подумать, то мы не обязаны создавать методы только для того, чтобы обойти неуклюжую особенность языка. И это даже без учёта, что такой рефакторинг не всегда возможен. Нет, нам нужно решение получше!
Представляем switch-выражения!
Начиная с Java 12 и выше, вы можете решить вышеуказанные проблемы следующим образом:
boolean result = switch (ternaryBool) {
case TRUE -> true;
case FALSE -> false;
case FILE_NOT_FOUND -> throw new UncheckedIOException(
"This is ridiculous!",
new FileNotFoundException());
// в ветке `default` уже нет необходимости
default -> throw new IllegalArgumentException("Seriously?!");
};
Я думаю, что это довольно очевидно: если ternartBool равен TRUE, то result будет присвоено true, а FALSE становится false.
Сразу возникают две мысли:
- switch теперь может иметь результат
- какие возможности предоставляют стрелки?
Прежде чем углубляться в детали новых возможностей, вначале я расскажу об этих двух аспектах.
Выражение vs оператора
Возможно, вы удивлены, что switch теперь является выражением. А чем же он был до этого? До Java 12 switch был оператором — императивной конструкцией, управляющей исполняющим потоком.
Думайте о различиях старой и новой версии switch, как о разнице между if и тернарным оператором. Они оба проверяют логическое условие и выполняют ту или иную ветку в зависимости от его результата.
Разница состоит в том, что if просто выполняет соответствующий блок, тогда как тернарный оператор возвращает какой-то результат:
if (condition) {
result = doThis();
} else {
result = doThat();
}
result = condition ? doThis() : doThat();
То же самое и у switch: до Java 12, если вы хотели вычислить значение и сохранить результат, то должны были либо присвоить его переменной, либо вернуть из метода, созданного специально для оператора switch.
Теперь же результат вычислений оператора switch может быть присвоен переменной.
Еще одно отличие заключается в том, что поскольку выражение является частью оператора, то оно должно заканчиваться точкой с запятой, в отличие от классического оператора switch.
Стрелка vs двоеточия
В самом начале статьи использовался пример с новым синтаксисом в лямбда-стиле со стрелкой между меткой и выполняющейся частью. Эквивалентный ему код без лямбда-стиля можно записать так:
boolean result = switch (ternaryBool) {
case TRUE:
yield true;
case FALSE:
yield false;
case FILE_NOT_FOUND:
throw new UncheckedIOException(
"This is ridiculous!",
new FileNotFoundException());
default:
throw new IllegalArgumentException("Seriously?!");
};
Обратите внимание, что вам нужно использовать новое ключевое слово yield, чтобы вернуть значение из ветки case (этот синтаксис появился в Java 13. В Java 12 вместо yield применялся break, т. е. break true вместо yield true, что выглядело странно).
Исторически сложилось, что метки с двоеточием определяют точку входа в блок операторов. С этого места начинается выполнение всего кода ниже, даже когда встречается другая метка (при отсутствии break). Механизм такой работы известен, как сквозной переход к следующему case. Для его прерывания нужен break или return.
Использование же стрелки позволяет выполнять только блок справа от нее. И никакого «проваливания».
Подробнее об эволюции switch
Несколько меток на case
Отсутствие break в case часто используется для применения одинакового поведение к веткам с разными метками. При этом программа будет переходить к следующему case, пока не наткнется на break. Из этого можно сделать вывод, что оператор switch в каждом case поддерживает наличие только одной метки:
switch (number) {
case 1:
case 2:
callMethod("few");
break;
default:
callMethod("many");
break;
}
А в новом switch один case может соответствовать нескольким меткам:
String result = switch (ternaryBool) {
case TRUE, FALSE -> "sane";
default -> "insane";
};
Поведение этого кода очевидно: TRUE и FALSE приводят к одному и тому же результату — вычисляется выражение «sane».
Поддержка других типов помимо Enum
Все примеры в этом посте в основном ориентированы на использование Enum. А как насчет других типов? На данный момент нет ни каких изменений: switch может работать с String, int, short, byte, char и их обертками. Хотя стоит отметить, что использование float, double и long находится на рассмотрении (предпоследний абзац).
Подробнее о стрелке
Начиная с Java 14, switch позволяет использовать лямбда-стрелку для «сопоставления» case с кодом:
switch (number) {
case 1 -> callMethod("one");
case 2 -> callMethod("two");
default -> callMethod("many");
}
Давайте рассмотрим два свойства, характерных для стрелочной формы записи разделителя:
- отсутствие сквозного перехода к следующему case
- блоки операторов
Отсутствие сквозного перехода к следующему case
Вот, что говорится в JEP 325 об этом:
…Текущий дизайн оператора switch в Java тесно связан с такими языками, как C и C++ и по умолчанию поддерживает сквозную семантику. Хотя этот традиционный способ управления часто полезен для написания низкоуровневого кода (такого как парсеры для двоичного кодирования), но поскольку switch используется и в коде более высокого уровня, ошибки такого подхода начинают перевешивать его гибкость.
Я полностью согласен и приветствую возможность использовать switch без поведения по умолчанию:
switch (ternaryBool) {
case TRUE, FALSE -> System.out.println("Bool was sane");
default -> System.out.println("Bool was insane");
};
Стрелка позволяет вывести «Bool was sane» в единственном экземпляре, в то время, как с двоеточием это же сообщение отобразилось бы дважды.
Блоки операторов
Как и в случае с лямбдами, стрелка может указывать либо на один оператор (как выше), либо на блок, выделенный фигурными скобками:
boolean result = switch (Bool.random()) {
case TRUE -> {
System.out.println("Bool true");
yield true;
}
case FALSE -> {
System.out.println("Bool false");
yield false;
}
case FILE_NOT_FOUND -> {
var ex = new UncheckedIOException(
"This is ridiculous!",
new FileNotFoundException());
throw ex;
}
default -> {
var ex = new IllegalArgumentException(
"Seriously?!");
throw ex;
}
};
Блоки необходимы для использования более одной строки кода в case. При этом они имеют дополнительное преимущество — позволяют создавать одинаковые имена переменных в разных ветках за счет локальной области видимости для каждой ветки.
Если вам показался необычным способ выхода из блоков с помощью yield, а не return, то это необходимо, чтобы избежать путаницы: return может быть неправильно истолкован, как выход из метода. Мы лишь завершаем работу switch, оставаясь в том же методе.
Подробнее о выражениях switch
Рассмотрим особенности использования switch в качестве выражения:
- множественные выражения
- ранний возврат
- охват всех значений (исчерпываемость)
Множественные выражения
Switch-выражения являются множественными выражениями. Это означает, что они не имеют своего собственного типа, но могут быть одним из нескольких типов. Наиболее часто в качестве таких выражений используются лямбда-выражения: s -> s + » «, могут быть и Function<String, String>, и Function<Serializable, Object> или UnaryOperator.
Тип switch-выражения определяется исходя из типов его веток, а также из места его использования. Если результат работы switch-выражения присваивается типизированной переменной, передается в качестве аргумента или используется в контексте, где известен точный тип (целевой тип), то все его ветки должны соответствовать этому типу. Вот, что мы делали до сих пор:
String result = switch (ternaryBool) {
case TRUE, FALSE -> "sane";
default -> "insane";
};
Как итог — switch присваивается переменной String result. Следовательно, String является целевым типом, и все ветки должны возвращать результат этого типа.
То же самое происходит и здесь:
Serializable serializableMessage = switch (bool) {
case TRUE, FALSE -> "sane";
// note that we don't throw the exception!
// but it's `Serializable`, so it matches the target type
default -> new IllegalArgumentException("insane");
};
А что произойдет сейчас?
// compiler infers super type of `String` and
// `IllegalArgumentException` ~> `Serializable`
var serializableMessage = switch (bool) {
case TRUE, FALSE -> "sane";
// note that we don't throw the exception!
default -> new IllegalArgumentException("insane");
};
Про применение типа var можно прочитать в статье: «26 рекомендаций по использованию типа var в Java».
Если целевой тип неизвестен из-за использования var, то он вычисляется путем нахождения наиболее конкретного супертипа из типов, создаваемых ветками.
Ранний возврат
Для выхода из оператора switch (да, для старой версии switch тоже теперь можно использовать оператор стрелку, но от этого он не становится выражением) и метода можно использовать return:
public String sanity(Bool ternaryBool) {
switch (ternaryBool) {
// `return` is only possible from block
case TRUE, FALSE -> { return "sane"; }
default -> { return "This is ridiculous!"; }
}
}
Оператор стрелку можно использовать для обычного switch не только для выхода из метода, что позволяет не использовать break и не проваливаться в последующие case:
int result = 0;
switch (sign) {
case '+' -> result = a + b;
case '^' -> {
result = 1;
for (int i = 1; i <= b; i++) {
result *= a;
}
}
default -> System.out.println("Мат. операция не поддерживается");
}
А вот внутри выражения использовать return уже не получится:
public String sanity(Bool ternaryBool) {
String result = switch (ternaryBool) {
// this does not compile - error:
// "return outside of enclosing switch expression"
case TRUE, FALSE -> { return "sane"; }
default -> { return "This is ridiculous!"; }
};
}
Это имеет смысл независимо от того, используете ли вы стрелку или двоеточие.
Охват всех значений (исчерпываемость)
Если вы используете switch в качестве оператора, тогда не имеет значения, охвачены все варианты или нет. Конечно, вы можете случайно пропустить case, и код будет работать неправильно, но компилятору все равно — вы, ваша IDE и ваши инструменты анализа кода останетесь с этим наедине.
Switch-выражения усугубляют эту проблему. Куда следует перейти switch, если нужная метка отсутствует? Единственный ответ, который может дать Java — это возвращать null для ссылочных типов и значение по умолчанию для примитивов. Это породило бы множество ошибок в основном коде.
Чтобы предотвратить такой исход, компилятор может помочь вам. Для switch-выражений компилятор будет настаивать, чтобы все возможные варианты были охвачены. Для каждого возможного значения в switch должна быть ветвь — это называется исчерпываемостью. Давайте посмотрим на пример, который может привести к ошибке компиляции:
// compile error:
// "the switch expression does not cover all possible input values"
boolean result = switch (ternaryBool) {
case TRUE -> true;
// no case for `FALSE`
case FILE_NOT_FOUND -> throw new UncheckedIOException(
"This is ridiculous!",
new FileNotFoundException());
};
Интересным является следующее решение: добавление ветки default, конечно, исправит ошибку, но это не является единственным решением — еще можно добавить case для FALSE.
// compiles without `default` branch because
// all cases for `ternaryBool` are covered
boolean result = switch (ternaryBool) {
case TRUE -> true;
case FALSE -> false;
case FILE_NOT_FOUND -> throw new UncheckedIOException(
"This is ridiculous!",
new FileNotFoundException());
};
Да, компилятор наконец-то сможет определить, охватываются ли все значения enum, что позволяет не использовать бесполезные значения в default!
Что касается исчерпываемости, я стараюсь избегать ветвей по умолчанию, когда это возможно, предпочитая получать ошибки компиляции, когда что-то меняется.
Хотя, это все же вызывает один вопрос. Что делать, если кто-то возьмет и превратит сумасшедший Bool в кватернионный (с четырьмя значениями) boolean, добавив четвертое значение? Если вы перекомпилируете switch-выражение для расширенного Bool, то получите ошибку компиляции (т. к. выражение больше не будет исчерпывающим). Чтобы отловить эту проблему, компилятор переходит в ветку default, которая ведет себя так же, как та, которую мы использовали до сих пор, вызывая исключение.
В настоящее время охват всех значений без ветки default работает только для enum, но когда switch в будущих версиях Java станет более мощным, он также сможет работать и с произвольными типами. Если метки case смогут не только проверять равенство, но и проводить сравнения (например _ < 5 -> …) — это позволит охватить все варианты для числовых типов.
Как пользоваться switch в современной Java
До этого мы рассматривали изменения, которые произошли до Java 14. Теперь обсудим то, что было реализовано после.
Паттерны (шаблоны)
Реализация сопоставления с образцом в switch все еще находится в процессе разработки, но есть три аспекта, которые особенно интересны по данной теме.
Паттерны типов
На момент написания статьи Java поддерживает только паттерны типов (Type Patterns) с паттернами деконструкции для записей (records), предложенными JEP 405. Их уже можно использовать в операторах if и switch:
Object obj = // ...
// работает с Java 16
if (obj instanceof String str)
callStringMethod(str);
else if (obj instanceof Number no)
callNumberMethod(no);
else
callObjectMethod(obj);
// работает (как превью) с JDK 17+
switch (obj) {
case String str -> callStringMethod(str);
case Number no -> callNumberMethod(no);
default -> callObjectMethod(obj);
}
Я думаю, что с такими возможностями switch станет более функциональным и интуитивным за счет того, что:
- более четко выражает намерение выполнить ровно одну ветвь на основе свойств obj
- компилятор проверяет исчерпываемость
- если необходимо вычислить значение, то использование switch в качестве выражения является более кратким
Применение уточнений (Clauses)
Уточнения (ранее — guarded patterns) расширяют возможности паттерна с помощью дополнительных логических проверок. Это может быть представлено следующим образом (синтаксис, придуманный мной):
String str = // ...
String length = switch (str) {
case str.length() > 42 -> "long";
case str.length() > 19 -> "medium";
case str.length() > 1 -> "small";
case null || str.length() == 0 -> "empty";
};
По мере того, как switch становится все более мощным, я предполагаю, что он начнет поглощать части кода, для реализации которых используется if-else-if.
Выводы
Из статьи мы узнали, что Java превращает switch в выражение, наделяя его новыми возможностями:
- теперь один case может соответствовать нескольким меткам
- новая стрелочная форма case … -> … следует синтаксису лямбда-выражений:
- допускаются однострочные операторы или блоки
- предотвращается сквозной переход к следующему case
- теперь все выражение оценивается, как значение, которое затем может быть присвоено переменной или передано, как часть более крупного оператора
- множественное выражение: если целевой тип известен, то все ветки должны ему соответствовать. В противном случае определяется конкретный тип, который соответствует всем веткам
- yield возвращает значение из блока
- для выражения switch, использующее enum, компилятор проверяет охват всех его значений. Если default отсутствует, добавляется ветка, которая вызывает исключение
- если шаблоны станут более функциональными, то они смогут сделать switch предпочтительнее if



