Представьте, что вы стоите на развилке, как богатырь с известной картины. Налево пойдёшь — коня потеряешь, направо пойдешь — знания обретёшь. Как запрограммировать такую ситуацию? Вы уже, скорее всего, знаете, что подобный выбор мы совершаем с помощью конструкций if-then и if-then-else.
if (turn_left) {
System.out.println(«Коня потеряешь»);
}
if (turn_right) {
System.out.println(“Знания обретёшь”);
}
else
System.out.println(“Так и будешь стоять?”);
А что, если таких дорожек не две, а 10? Есть дорожка «совсем направо», «чуть левее», «ещё чуть-чуть левее» и так далее, в количестве 10 штук?
Представьте, как разрастётся ваш if-then-else код в таком варианте!
if (вариант1)
{… }
else if (вариант2)
{…}
…
else if (вариантN) ….
Итак, у вас не одна развилка условий, а несколько, скажем, 10 (тут важно, что число развилок ограничено). Для таких ситуаций есть специальный оператор выбора — switch case java.
switch (ВыражениеДляВыбора) {
case (Значение1):
Код1;
break;
case (Значение2):
Код2;
break;
...
case (ЗначениеN):
КодN;
break;
default:
КодВыбораПоУмолчанию;
break;
}
Порядок выполнения в операторе следующий:
- Вычисляется ВыражениеДляВыбора. Далее оператор switch сравнивает полученное выражение с очередным Значением (в порядке перечисления).
- Если ВыражениеДляВыбора совпало со Значением, то выполняется код, идущий после двоеточия.
- Если встречается конструкция break — то управление передается за пределы команды switch.
- Если совпадений ВыражениеДляВыбора и Значений не выявлено, то управление передаётся КодуВыбораПоУмолчанию.
Тип ВыражениеДляВыбора для оператора выбора switch в Java должен быть одним из следующих:
- byte, short, char, int.
- Их обёртки Byte, Short, Character, Integer.
- String (начиная с Java 7).
- Перечисление (Enum).
- блок default — необязательный, тогда в случае отсутствия совпадений ВыраженияДляВыбора и Значений не будет выполнено никаких действий.
- break не является обязательным, если его нет – код продолжит выполнение (игнорируя дальнейшие сравнения значений в блоках case) до первого встреченного
break
или до конца оператора switch. - если необходимо выполнять один и тот же код для нескольких вариантов выбора, для исключения дублирования перед ним указываем несколько соответствующих значений в подряд идущих блоках case.
Перейдем к практике использования оператора switch в Java
Не волнуйтесь, с теорией покончили, и, после дальнейших примеров станет всё гораздо понятнее. Итак, приступим. Давайте рассмотрим пример из астрономии о планетах Солнечной системы. В соответствии с последними международными положениями исключим Плутон (за свойства ее орбиты). Вспомним, что планеты у нас расположены от Солнца в следующей последовательности: Меркурий, Венера, Земля, Марс, Юпитер, Сатурн, Уран и Нептун. Создадим Java метод, который получает на вход порядковый номер планеты (относительно удалённости от Солнца), а на выходе выдает основной состав атмосферы этой планеты в виде списка List<String>. Напомню, у некоторых планет – схожий состав атмосферы. Так, Венера и Марс содержат в основном углекислый газ, у Юпитера и Сатурна она состоят из водорода и гелия, а Уран и Нептун вдобавок к последней паре газов также имеет метан. Наша функция:
public static List<String> getPlanetAtmosphere(int seqNumberFromSun) {
List<String> result = new ArrayList<>();
switch (seqNumberFromSun) {
case 1: result.add("Нет атмосферы");
break;
case 2:
case 4: result.add("Углекислый газ");
break;
case 3: result.add("Углекислый газ");
result.add("Азот");
result.add("Кислород");
break;
case 5:
case 6: result.add("Водород");
result.add("Гелий");
break;
case 7:
case 8: result.add("Метан");
result.add("Водород");
result.add("Гелий");
break;
default:
break;
}
return result;
}
Обратите внимание: идентичным по составу атмосфер планетам мы сопоставили один и тот же код. А сделали мы это за счёт подряд идущих конструкций case.
Итого, если мы хотим получить состав атмосферы нашей с вами родной планеты, вызываем наш метод с параметром 3:
getPlanetAtmosphere(3).
System.out.println(getPlanetAtmosphere(3)) вернет нам [Углекислый газ, Азот, Кислород].
Эксперимент с break
Что получится, если мы уберем все операторы break? Попробуем на практике:
public static List<String> getPlanetAtmosphere(int seqNumberFromSun) {
List<String> result = new ArrayList<>();
switch (seqNumberFromSun) {
case 1: result.add("Нет атмосферы");
case 2:
case 4: result.add("Углекислый газ");
case 3: result.add("Углекислый газ");
result.add("Азот");
result.add("Кислород");
case 5:
case 6: result.add("Водород");
result.add("Гелий");
case 7:
case 8: result.add("Метан");
result.add("Водород");
result.add("Гелий");
default:
}
return result;
}
Если мы распечатаем результат работы метода System.out.println(getPlanetAtmosphere(3))
, то наша родная планета окажется не такой уж пригодной для жизни. Или пригодной? Судите сами:
[Углекислый газ, Азот, Кислород, Водород, Гелий, Метан, Водород, Гелий],
Почему так получилось? Программа выполнила все case после первого совпадения и до конца блока switch.
Излишняя оптимизация break
Заметим, что мы можем усовершенствовать метод иным расположением директив break и вариантов выбора
public static List<String> getPlanetAtmosphere(int seqNumberFromSun) {
List<String> result = new ArrayList<>();
switch (seqNumberFromSun) {
case 1: result.add("Нет атмосферы");
break;
case 3: result.add("Азот");
result.add("Кислород");
case 2:
case 4: result.add("Углекислый газ");
break;
case 7:
case 8: result.add("Метан");
case 5:
case 6: result.add("Водород");
result.add("Гелий");
}
return result;
}
Выглядит короче, не так ли? Мы сократили общее число операторов, поигравшись с порядком следования case и перегруппировкой. Теперь каждый вид газа добавляется в список только в одной строчке кода.
Листинг последнего примера метода показан только для демонстрации работы, крайне не рекомендуется писать в подобном стиле. Если автору (а тем более сторонним программистам) схожего кода предстоит его сопровождать, то будет весьма тяжело восстановить логику формирования блоков выбора и выполняемого кода для оператора switch java.
Отличия от if
При внешней схожести операторов if и switch не забывайте, что выбор вариантов выполнения оператор множественного выбора switch основывает на КОНКРЕТНОМ ЗНАЧЕНИИ, тогда как в if. может быть любое логическое выражение. Учитывайте данный факт, проектируя ваш код. Давайте немного подробнее рассмотрим нововведения для switch в разных версиях Java.Switch в Java 7
До Java 7 в качестве значения для переключателя можно было использовать byte, short, char и int примитивы. Также была поддержка enum и оберток перечисленных выше примитивных типов: Character, Byte, Short, и Integer. Но ведь часто нам нужно найти значение java switch string! Как бы это выглядело в Java 6:
DayOfWeek day = DayOfWeek.fromValue("Thursday");
switch (day) {
case MONDAY:
System.out.println("Today is windy !");
break;
case THURSDAY:
System.out.println("Today is sunny !");
break;
case WEDNESDAY:
System.out.println("Today is rainy!");
break;
default:
System.out.println("Oooops, something wrong !");
И enum:
public enum DayOfWeek {
MONDAY("Monday"),
THURSDAY("Thursday"),
WEDNESDAY("Wednesday"),
NOT_FOUND("Not found");
private final String value;
DayOfWeek(final String value) {
this.value = value;
}
public static DayOfWeek fromValue(String value) {
for (final DayOfWeek dayOfWeek : values()) {
if (dayOfWeek.value.equalsIgnoreCase(value)) {
return dayOfWeek;
}
}
return NOT_FOUND;
}
}
Но начиная с Java 7 добавилась возможно использовать тип String как значение для переключателя switch:
String day = "Thursday";
switch (day) {
case "Monday":
System.out.println("Today is windy !");
break;
case "Thursday":
System.out.println("Today is sunny !");
break;
case "Wednesday":
System.out.println("Today is rainy!");
break;
default:
System.out.println("Oooops, something wrong !");
}
Несмотря на новые возможности, подход с использованием enum более гибок и рекомендуем к использованию: этот enum мы можем переиспользовать много раз.
Switch в Java 12
В Java 12 были улучшены выражения Switch для сопоставления с образцом. Если использовать Switch как и в примере выше, для задания значения некоторой переменной мы должны были вычислить значение и присвоить заданной переменной, а затем использовать break:
int count = 2;
int value;
switch (count) {
case 1:
value = 12;
break;
case 2:
value = 32;
break;
case 3:
value = 52;
break;
default:
value = 0;
}
Но благодаря возможностям 12-й версии Java мы можем переписать данное выражение следующим образом:
int value = switch (count) {
case 1:
break 12;
case 2:
break 32;
case 3:
break 52;
default:
break 0;
};
Давайте немного пройдёмся по измененным моментам:
Если ранее мы задавали переменной значение внутри блоков case, так как сам оператор switch не мог ничего возвращать, сейчас такая возможность у нас есть, и мы непосредственно с помощью switch возвращаем значение.
Ранее справа от break у нас уже ничего не могло стоять, а сейчас мы его используем как оператор return для возвращения значения нашим switch. Метки с двоеточием отмечают точку входа в блок операторов. То есть с того места начинается выполнение всего кода ниже, даже тогда, когда встречается другая метка.
Как итог — сквозной переход от метки к метке, который еще называют проваливанием (fall-through).
int count = 2;
int value = switch (count) {
case 1 -> 12;
case 2 -> 32;
case 3 -> 52;
default -> 0;
};
Код стал значительно проще, не правда ли?
И ещё: лямбда оператор может служить и типичным аналогом двоеточия, после которого идёт целый блок с некоторыми операциями:
int count = 2;
int value = switch (count) {
case 1 -> {
//некоторые вычислительные операции...
break 12;
}
case 2 -> {
//некоторые вычислительные операции...
break 32;
}
case 3 -> {
//некоторые вычислительные операции...
break 52;
}
default -> {
//некоторые вычислительные операции...
break 0;
}
};
Ну а что если у нас для некоторых кейсов возвращаемое значение будет одинаковым?
Получится, что у нас фактически одинаковые кейсы для некоторых разных значений. Как бы это можно было бы сократить с помощью новых возможностей Java 12:
int count = 2;
int value = switch (count) {
case 1, 3, 5 -> 12;
case 2, 4, 6 -> 52;
default -> 0;
};
Switch в Java 13
В Java 13 изменился способ возврата значения из switch. Если в java 12 возвращаемое значение мы писали после break, который служил у нас как return для блока switch, то сейчас возвращать значение мы будем с помощью слова yield. Смотрим:
int value = switch (count) {
case 1:
yield 12;
case 2:
yield 32;
case 3:
yield 52;
default:
yield 0;
};
В тоже время, код, написанный на java 12 c использованием break для возврата, компилироваться не будет((
Break будет использоваться, но в тех ситуациях, когда нам не нужно ничего возвращать.
Итого
- Используйте оператор case при числе ветвлений более двух, чтобы не загромождать if-структурами код.
- Не забывайте завершать логический блок каждой ветки соответствующего конкретному значению (блок case) вызовом break.
- Оператор switch помимо некоторых примитивных типов, в качестве выражения может использовать также типы Enum и String.
- Помните про блок default – употребляйте его для обработки незапланированных значений выбора.
- Для оптимизации производительности переместите ветки кода с наиболее часто встречающимися вариантами выбора к началу блока switch.
- Не увлекайтесь «оптимизацией» за счёт удаления break в конце блока выбора case – такой код сложен для понимания, и, как следствие, тяжело сопровождать при его развитии.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ