Представьте, что вы стоите на развилке, как богатырь с известной картины. Налево пойдёшь — коня потеряешь, направо пойдешь — знания обретёшь. Как запрограммировать такую ситуацию? Вы уже, скорее всего, знаете, что подобный выбор мы совершаем с помощью конструкций 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 будет использоваться, но в тех ситуациях, когда нам не нужно ничего возвращать.

Итого

  • Используйте оператор case при числе ветвлений более двух, чтобы не загромождать if-структурами код.
  • Не забывайте завершать логический блок каждой ветки соответствующего конкретному значению (блок case) вызовом break.
  • Оператор switch помимо некоторых примитивных типов, в качестве выражения может использовать также типы Enum и String.
  • Помните про блок default – употребляйте его для обработки незапланированных значений выбора.
  • Для оптимизации производительности переместите ветки кода с наиболее часто встречающимися вариантами выбора к началу блока switch.
  • Не увлекайтесь «оптимизацией» за счёт удаления break в конце блока выбора case – такой код сложен для понимания, и, как следствие, тяжело сопровождать при его развитии.