Как осуществляется выход из оператора switch
Конструкция switch-case позволяет сравнить некоторое выражение с набором значений. Она имеет следующую форму:
После ключевого слова switch в скобках идет сравниваемое выражение. Значение этого выражения последовательно сравнивается со значениями после оператора сase . И если совпадение будет найдено, то будет выполняться определенный блок сase .
Стоит отметить, что сравниваемое выражение в switch должно представлять один из целочисленных или символьных типов или перечисление (рассматриваются далее).
В конце конструкции switch может стоять блок default . Он необязателен и выполняется в том случае, если значение после switch не соответствует ни одному из операторов case. Например:
Чтобы избежать выполнения последующих блоков case/default, в конце каждого блока ставится оператор break . То есть в данном случае будет выполняться оператор
После выполнения оператора break произойдет выход из конструкции switch..case, и остальные операторы case будут проигнорированы. Поэтому на консоль будет выведена следующая строка
Стоит отметить важность использования оператора break . Если мы его не укажем в блоке case , то после этого блока выполнение перейдет к следующему блоку case. Например, уберем из предыдущего примера все операторы break:
В этом случае опять же будет выполняться оператор case 2: , так как переменная x=2. Однако так как этот блок case не завершается оператором break, то после его завершения будет выполняться набор инструкций после case 3: даже несмотря на то, что переменная x по прежнему равна 2. В итоге мы получим следующий консольный вывод:
Совмещение условий
Можно определять для нескольких меток case один набор инструкций:
Здесь если x=1 или x=2 , то выполняется одна и та же инструкция std::cout << «x = 1 or 2» << «\n» . Аналогично для вариантов x=3 и x=4 также определена общая инструкция.
Переменные в блоках case
Определение переменных в блоках case , возможно, встречается нечасто. Однако может вызвать затруднения. Так, если переменная определяется в блоке case, то все инструкции блока помещаются в фигурные скобки (для блока default это не требуется):
Блок switch с инициализацией переменной
Иногда в конструкции switch для различных промежуточных вычислений необходимо определить переменную. Для этой цели начиная со стандарта C++17 язык С++ поддерживает особую форму конструкции switch :
Подобная форма также принимает выражение, значение которого сравнивается с константами после операторов case . Но теперь перед выражением еще может идти определение и инициализация переменной. Например:
В данном случае в конструкции switch определяется переменная k , которая доступна только в рамках этой конструкции switch . В качестве выражения используется значение переменной op , которая представляет знак операции. И в зависимости от этого значения, выполняем определенную операцию с переменными n и k.
Полное руководство по switch в Java
Старый добрый switch был в Java с первого дня. Мы все используем его и привыкли к нему — особенно к его причудам (кого-нибудь еще раздражает break?). Но начиная с Java 12, ситуация начала меняться: switch вместо оператора стал выражением:
Результат работы switch-выражения теперь можно сохранять в переменную; ушла необходимость использовать break в каждой ветке case благодаря лямбда-синтаксису и многое другое.
Когда дело доходит до switch после Java 14, необходимо выбрать стиль его использования:
- оператор или выражение (с Java 14)
- двоеточия или стрелки (с Java 14)
- метки или шаблоны (3-й превью в Java 19)
В этом руководстве я расскажу обо всем, что необходимо знать о switch-выражениях, и как их лучше всего использовать в современной Java.
Недостатки оператора switch
Прежде, чем мы перейдем к обзору нововведений, давайте рассмотрим один пример кода. Допустим, мы столкнулись с «ужасным» тернарным boolean и хотим преобразовать его в обычный boolean. Вот один из способов сделать это:
Реализация данного кода хромает: наличие break в каждой ветке, которые легко забыть; можно не учесть все возможные значения ternaryBool (забыть реализовать какой-то case); с переменной result не все гладко — область видимости не соответствует ее использованию; нельзя объявить в разных ветках переменные с одинаковым именем. Согласитесь, что данное решение выглядит крайне громоздко и неудобно — тут явно есть, что улучшить.
А вот пример попроще, демонстрирующий похожие проблемы:
Давайте попробуем устранить все недостатки, поместив switch в отдельный метод:
Так намного лучше: отсутствует фиктивная переменная result, нет break, загромождающих код и сообщений компилятора об отсутствии default (даже если в этом нет необходимости, как в данном случае).
Но если подумать, то мы не обязаны создавать методы только для того, чтобы обойти неуклюжую особенность языка. И это даже без учёта, что такой рефакторинг не всегда возможен. Нет, нам нужно решение получше!
Представляем switch-выражения!
Начиная с Java 12 и выше, вы можете решить вышеуказанные проблемы следующим образом:
Я думаю, что это довольно очевидно: если ternartBool равен TRUE, то result будет присвоено true, а FALSE становится false.
Сразу возникают две мысли:
- switch теперь может иметь результат
- какие возможности предоставляют стрелки?
Прежде чем углубляться в детали новых возможностей, вначале я расскажу об этих двух аспектах.
Выражение vs оператора
Возможно, вы удивлены, что switch теперь является выражением. А чем же он был до этого? До Java 12 switch был оператором — императивной конструкцией, управляющей исполняющим потоком.
Думайте о различиях старой и новой версии switch, как о разнице между if и тернарным оператором. Они оба проверяют логическое условие и выполняют ту или иную ветку в зависимости от его результата.
Разница состоит в том, что if просто выполняет соответствующий блок, тогда как тернарный оператор возвращает какой-то результат:
То же самое и у switch: до Java 12, если вы хотели вычислить значение и сохранить результат, то должны были либо присвоить его переменной, либо вернуть из метода, созданного специально для оператора switch.
Теперь же результат вычислений оператора switch может быть присвоен переменной.
Еще одно отличие заключается в том, что поскольку выражение является частью оператора, то оно должно заканчиваться точкой с запятой, в отличие от классического оператора switch.
Стрелка vs двоеточия
В самом начале статьи использовался пример с новым синтаксисом в лямбда-стиле со стрелкой между меткой и выполняющейся частью. Эквивалентный ему код без лямбда-стиля можно записать так:
Обратите внимание, что вам нужно использовать новое ключевое слово 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 один case может соответствовать нескольким меткам:
Поведение этого кода очевидно: TRUE и FALSE приводят к одному и тому же результату — вычисляется выражение «sane».
Подробнее о стрелке
Начиная с Java 14, switch позволяет использовать лямбда-стрелку для «сопоставления» case с кодом:
Давайте рассмотрим два свойства, характерных для стрелочной формы записи разделителя:
- отсутствие сквозного перехода к следующему case
- блоки операторов
Отсутствие сквозного перехода к следующему case
Вот, что говорится в JEP 325 об этом:
Я полностью согласен и приветствую возможность использовать switch без поведения по умолчанию:
Стрелка позволяет вывести «Bool was sane» в единственном экземпляре, в то время, как с двоеточием это же сообщение отобразилось бы дважды.
Блоки операторов
Как и в случае с лямбдами, стрелка может указывать либо на один оператор (как выше), либо на блок, выделенный фигурными скобками:
Блоки необходимы для использования более одной строки кода в case. При этом они имеют дополнительное преимущество — позволяют создавать одинаковые имена переменных в разных ветках за счет локальной области видимости для каждой ветки.
Если вам показался необычным способ выхода из блоков с помощью yield, а не return, то это необходимо, чтобы избежать путаницы: return может быть неправильно истолкован, как выход из метода. Мы лишь завершаем работу switch, оставаясь в том же методе.
Подробнее о выражениях switch
И последнее, но не менее важное — особенности использования switch в качестве выражения:
- множественные выражения
- ранний возврат
- охват всех значений (исчерпываемость)
Множественные выражения
Switch-выражения являются множественными выражениями. Это означает, что они не имеют своего собственного типа, но могут быть одним из нескольких типов. Наиболее часто в качестве таких выражений используются лямбда-выражения: s -> s + » «, могут быть и Function<String, String>, и Function<Serializable, и Object> или UnaryOperator.
Тип switch-выражения определяется исходя из типов его веток, а также из места его использования. Если результат работы switch-выражения присваивается типизированной переменной, передается в качестве аргумента или используется в контексте, где известен точный тип (целевой тип), то все его ветки должны соответствовать этому типу. Вот, что мы делали до сих пор:
Как итог — switch присваивается переменной String result. Следовательно, String является целевым типом, и все ветки должны возвращать результат этого типа.
То же самое происходит и здесь:
А что произойдет сейчас?
Про применение типа var можно прочитать в статье: «26 рекомендаций по использованию типа var в Java».
Если целевой тип неизвестен из-за использования var, то он вычисляется путем нахождения наиболее конкретного супертипа из типов, создаваемых ветками.
Ранний возврат
Следствием различия между выражением и оператором switch является то, что вы можете использовать return для выхода из оператора switch:
А вот внутри выражения использовать return уже не получится:
Это имеет смысл независимо от того, используете ли вы стрелку или двоеточие.
Охват всех значений (исчерпываемость)
Если вы используете switch в качестве оператора, тогда не имеет значения, охвачены все варианты или нет. Конечно, вы можете случайно пропустить case, и код будет работать неправильно, но компилятору все равно — вы, ваша IDE и ваши инструменты анализа кода останетесь с этим наедине.
Switch-выражения усугубляют эту проблему. Куда следует перейти switch, если нужная метка отсутствует? Единственный ответ, который может дать Java — это возвращать null для ссылочных типов и значение по умолчанию для примитивов. Это породило бы множество ошибок в основном коде.
Чтобы предотвратить такой исход, компилятор может помочь вам. Для switch-выражений компилятор будет настаивать, чтобы все возможные варианты были охвачены. Для каждого возможного значения переменной switch должна быть ветвь — это называется исчерпываемостью. Давайте посмотрим на пример, который может привести к ошибке компиляции:
Интересным является следующее решение: добавление ветки default, конечно, исправит ошибку, но это не является единственным решением — еще можно добавить case для FALSE.
Да, компилятор наконец-то сможет определить, охватываются ли все значения 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:
Я думаю, что с такими возможностями switch станет более функциональным и интуитивным за счет того, что:
- более четко выражает намерение выполнить ровно одну ветвь на основе свойств obj
- компилятор проверяет исчерпываемость
- если необходимо вычислить значение, то использование switch в качестве выражения является более кратким
Применение уточнений (Clauses)
Уточнения (ранее — guarded patterns) расширяют возможности паттерна с помощью дополнительных логических проверок. Это может быть представлено следующим образом (синтаксис, придуманный мной):
По мере того, как switch становится все более мощным, я предполагаю, что он начнет поглощать части кода, для реализации которых используется if-else-if.
Выводы
Из статьи мы узнали, что Java превращает switch в выражение, наделяя его новыми возможностями:
C++. Оператор выбора switch
Оператор выбора switch позволяет выбрать один вариант хода решения задачи из нескольких в зависимости от значения выражения. Таким образом, обеспечивается многонаправленное разветвление в программе.
Оператор switch может быть заменен оператором if . Однако, в некоторых случаях использование оператора switch может быть более эффективным, чем использование оператора if .
2. Общая форма описания оператора выбора switch . Ключевые слова switch , case , break , default
Общая форма записи оператора switch следующая
- выражение – некоторое выражение целочисленного ( int ) или символьного типа ( char );
- константа1 , константа2 , …, константаN – константные значения, с которыми сравнивается значение выражения. Константные значения должны быть целочисленного или символьного типа;
- последовательность_операторов1 , последовательность_операторов2 , …, последовательность_операторовN – соответствующая последовательность операторов (инструкций), которые связаны с соответствующим условием;
- последовательность_операторов – последовательность операторов, которая выполняется в случае, если не найдено ни одно совпадение значения выражения со значением констант.
Оператор switch работает следующим образом. Сначала вычисляется значение выражение. Потом это значение последовательно сравнивается с константами из заданного списка. Как только найдено совпадение для одного из условий сравнения (ветвь case ), выполняется последовательность инструкций, которые связаны с этим сравнением. Если не найдено ни одного совпадения, то выполняются операторы, которые следуют после ключевого слова default .
Ключевое слово case используется для того, чтобы указать значение константы, при совпадении с которой значения выражения, будет выполняться соответствующая последовательность операторов.
Операция break прерывает (заканчивает) выполнение кода, который определен инструкцией switch .
Операции, которые размещены после ключевого слова default , выполняются в том случае, если ни одна из case-констант не совпадает с результатом вычисления switch -выражения.
Ключевое слово default и соответствующая последовательность операторов могут быть опущены. В этом случае, если не выполняется ни одна из ветвей после слова case , выполнение передается следующему оператору, который следует за оператором switch .
3. Примеры использования оператора выбора switch
Пример 1. Даны значения n = 1..7, что есть номером дня недели. По значению n определить, выходной этот день или рабочий. Результат записать в переменную fDayOff типа bool .
Фрагмент кода, который решает данную задачу.
Другой, более компактный вариант решения данной задачи.
Еще один вариант решения данной задачи
Пример 2. Дано целое число n = 1..3, которое есть номером функции. По значению переменной n вычислить значение соответствующей функции:
Фрагмент кода, который решает данную задачу с помощью сокращенной формы оператора switch .
4. Особенности использования операции break в операторе switch
В операторе switch операция break не является обязательной.
Если операция break присутствует, то происходит выход из оператору switch и выполнение следующего за ним оператора. Если операция break в case -ветви отсутствует, тогда выполняются все операторы, которые связаны с данной case -ветвью, а также все операторы, которые следуют сразу после нее до тех пор, пока не встретится другая инструкция break или не будет достигнут конец switch -оператора.
5. Вложенные инструкции switch . Общая форма описания
Оператор switch может иметь разные типы вложения. В этом случае вложенный оператор (нижнего уровня) switch используется как часть case -последовательности внешнего (верхнего уровня) оператора switch .
Оператор выбора (переключатель) switch
Начнём, как обычно, с примера задачи, которую поможет решить данный оператор.
Задача на оператор switch . Программа-тест.
Написать программу, которая проверяет знание начала и конца Второй мировой войны. Выберите правильный вариант ответа. Начало и конец Второй мировой войны:
a. 01.09.1939 — 2.09.1945
b. 01.09.1939 — 9.05.1945
c. 22.06.1941 — 9.05.1945
d. 22.06.1941 — 2.09.1945
При выборе правильного варианта вывести GOOD , при выборе неправильного BAD . Если выбран вариант, которого нет в ответах, вывести ERROR .
Посмотрите на эту заготовку. Там есть две строчки, которые вам пока неизвестны. В комментариях я кратко подписал, для чего эти строчки нужны.
Используйте заголовочный файл locale.h и функцию setlocale в своих программах, если хотите использовать русские символы.
Шаблон оператора switch
Общий шаблон для оператора switch.
Работает он следующим образом.
Вычисляется, если необходимо, выражение, записанное в скобках. Именно от значения этого выражения зависит, какая из ветвей программы будет выполняться. Сразу же отметим первое ограничение, которое накладывается на это выражение.
В качестве выражения может использоваться переменная типа int , переменная типа char или любое выражение, результат которого будет иметь данные типы.
Примеры правильных и неправильных выражений:
После того как выражение вычислено, его результат проверяется на соответствие с ключ_1 , потом с ключ_2 , потом с ключ_3 и т.д.
Если выражение и какой-нибудь ключ совпали, то выполняются операторы, которые расположены после данного ключа.
Внимательно следите за тем, чтобы у каждый ветки case был свой уникальный ключ.
Если совпадений не обнаружено, то выполняются операторы, записанные в ветке default .
Надеюсь, общий принцип работы оператора switch ясен. А если не ясен, посмотрите на следующую картинку. На ней я нарисовал условную блок-схему для этого оператора и нашей задачки.

Рис.1 Условная схема оператора выбора switch
Теперь давайте дополним нашу программу. В качестве выражения-переключателя у нас будет выступать переменная answer. В качестве ключей буквы a , b , c и d . Получится вот такой код.
Оператор break
Теперь давайте потестируем нашу программу. Запустим её и введём правильный ответ. Результат работы:

Рис.2 Результат работы программы Листинг 3.
Ууппс… Не ожидали? А я ожидал. Это одна из особенностей оператора switch , о которой я вам пока не рассказал. Эта особенность называется сквозное выполнение . Поясню, как это работает.
После того как нашлось совпадение в одной из веток case, если явно не указан выход из оператора switch , то выполнятся последовательно все остальные ветки, записанные в коде после той, в которой произошло совпадение. Если в какой-то из них будет оператор выхода, то выполнение оператора switch завершится.
В нашем случае мы ввели символ a, программа выполнила первую ветку, и т.к. нет оператора выхода, то стали выполняться остальные ветки. При этом проверка на совпадение с ключами этих веток игнорируются. Чуть ниже я покажу, как можно использовать эту особенность для пользы дела.
А теперь собственно об операторе безусловного выхода. Наверное вы уже догадались, что это оператор break . Данный оператор завершает выполнение ближайшего к нему оператора switch , в котором он расположен.