User Marat Sadykov
Marat Sadykov
41 уровень

Оператор switch в Java

Статья из группы Java Developer
Представьте, что вы стоите на развилке, как богатырь с известной картины. Налево пойдёшь — коня потеряешь, направо пойдешь — знания обретёшь. Как запрограммировать такую ситуацию? Вы уже, скорее всего, знаете, что подобный выбор мы совершаем с помощью конструкций 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;
};
Давайте немного пройдёмся по измененным моментам:
  1. Если ранее мы задавали переменной значение внутри блоков case, так как сам оператор switch не мог ничего возвращать, сейчас такая возможность у нас есть, и мы непосредственно с помощью switch возвращаем значение.

  2. Ранее справа от break у нас уже ничего не могло стоять, а сейчас мы его используем как оператор return для возвращения значения нашим switch. Метки с двоеточием отмечают точку входа в блок операторов. То есть с того места начинается выполнение всего кода ниже, даже тогда, когда встречается другая метка.

    Как итог — сквозной переход от метки к метке, который еще называют проваливанием (fall-through).

Оператор switch в Java - 2Для завершения такого прохода нужно либо полностью пройтись по всем элементам, либо использовать break или return. Нововведение в java 12 даёт нам возможность использовать лямбда оператор, который в свою очередь гарантирует , что будет выполнен только код справа от нее. Без всякого "проваливания". Как будем выглядеть предыдущий пример в таком случае:

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 для возврата, компилироваться не будет(( Оператор switch в Java - 3Break будет использоваться, но в тех ситуациях, когда нам не нужно ничего возвращать. Оператор switch в Java - 4

Итого

  • Используйте оператор case при числе ветвлений более двух, чтобы не загромождать if-структурами код.
  • Не забывайте завершать логический блок каждой ветки соответствующего конкретному значению (блок case) вызовом break.
  • Оператор switch помимо некоторых примитивных типов, в качестве выражения может использовать также типы Enum и String.
  • Помните про блок default – употребляйте его для обработки незапланированных значений выбора.
  • Для оптимизации производительности переместите ветки кода с наиболее часто встречающимися вариантами выбора к началу блока switch.
  • Не увлекайтесь «оптимизацией» за счёт удаления break в конце блока выбора case – такой код сложен для понимания, и, как следствие, тяжело сопровождать при его развитии.
Комментарии (47)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Aleksei Reinsalu Уровень 18, Таллинн, Эстония
15 ноября 2021
Можно исправить логическую опечатку "Уран и Нептун вдобавок к последней паре газов также имеЕт метан" ? А то она в самый ответственный момент, когда вот-вот все поймешь, мешается.
Anonymous #2759139 Уровень 8, Львов, Ukraine
30 октября 2021
А можна в свич сравнивать 2 перемених
Ильшат Уровень 18, Сибай, Россия
14 октября 2021
в чем смысл yield? если нет разницы меду break и return. тем более что ломает программы в разных версиях java.
YesOn Уровень 6, Томск, Россия
16 сентября 2021
А какая версия Java используется по умолчанию в учебных материалах на JAVARUSH? Я встречал задачи, в которых break спокойно применяли в правильном решении и всё работало.
YesOn Уровень 6, Томск, Россия
16 сентября 2021
Статья интересная и познавательная, спасибо!

Switch в Java 13
В Java 13 изменился способ возврата значения из switch.

Если в java 12 возвращаемое значение мы писали после break, 
который служил у нас как return для блока switch, то сейчас возвращать 
значение мы будем с помощью слова yield.
В тоже время, код, написанный на java 12 c использованием break для
 возврата, компилироваться не будет((
Именно поэтому и из-за таких "фишек", многие бизнесы остаются на Java 8?
Xmel Уровень 16, Earth Orbit
15 августа 2021
Очень интересная и крутая статья! Спасибо огромное автору. Про такие фишки в switch'е реально не знал. Кстати про возврат значения из этого оператора... да break уже не катит, т.к. break используется теперь ещё как метка для перехода (на основании Java SE 9 (JDK 9), Г. Шилдт "Полное руководство" (10-е изд.), стр. 157-159) как goto в C++.
Артем Уровень 37, Москва
9 августа 2021
Народ, помогите прояснить один момент работы со switch: в одном case я создаю объект, допустим SimpleDateFormat formatter = new SimpleDateFormat(); и допустим в другом case мне нужен идентичный объект, IDE ругается, если я пытаюсь инициализировать переменную с тем же именем, значит, второй case имеет доступ к объектам из других case'ов и я могу ими пользоваться? но если первый case не сработает, то объект не будет создан или как?
Sergey Kornilov Уровень 30, Petropavlovsk, Казахстан
28 июля 2021
Язык Java постоянно развивается...не поспеть за нововведениями.
(:ArbyziK:) Уровень 38, San Jose, Россия
14 мая 2021
Хм,не уверен,что я внимательно читал статью, но не заметил здесь перечисления в case Допустим,есть времена года и месяца,если месяц равен от 6 до 9, то возращаем лето. Вот,как такое перечисление можно реализовать в case?
Velind Уровень 21, Воронеж
12 мая 2021
Отличная статья, особенно про сравнение возможностей в различных версия языка