Какие типы данных не могут быть в качестве условия switch-case; в java?
Какие могут:
char. byte, short, int, Character, Byte, Short, Integer или enum
Начиная с java 8 также может быть String.
Остальные, соответственно, не могут.
Источник: Java language specification
Остальные ответы
Похожие вопросы
Ваш браузер устарел
Мы постоянно добавляем новый функционал в основной интерфейс проекта. К сожалению, старые браузеры не в состоянии качественно работать с современными программными продуктами. Для корректной работы используйте последние версии браузеров Chrome, Mozilla Firefox, Opera, Microsoft Edge или установите браузер Atom.
Полное руководство по switch в Java
Старый добрый 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)
Давайте попробуем устранить все недостатки, поместив 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)
А в новом 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, и Function или 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 > 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
Java: Конструкция Switch
Многие языки используют не только условную конструкцию if , но и switch в дополнение к ней. Конструкция switch — это специализированная версия if , созданная для некоторых особых ситуаций.
Например, ее стоит использовать там, где есть цепочка if else с проверками на равенство:
if (status.equals("processing")) < // Делаем раз >else if (status.equals("paid")) < // Делаем два >else if (status.equals("new")) < // Делаем три >else < // Делаем четыре >Эта составная проверка обладает одной отличительной чертой: каждая ветка здесь — это проверка значения переменной status . Конструкция switch позволяет записать этот код короче и выразительнее:
switch (status) < case "processing": // Делаем раз break; case "paid": // Делаем два break; case "new": // Делаем три break; default: // else // Делаем четыре >С точки зрения количества элементов, switch — довольно сложная конструкция. В нее входят:
- Внешнее описание с ключевым словом switch . В нем два элемента:
- Переменная, по значениям которой switch будет выбирать поведение
- Фигурные скобки для вариантов выбора
Фигурные скобки в switch не определяют блок кода, как это было в других местах. Внутри допускается только тот синтаксис, который показан выше — там можно использовать case или default . А вот внутри каждого case (и default ) ситуация другая. Здесь можно выполнять любой произвольный код:
switch (count) < case 1: // Делаем что-то полезное break; case 2: // Делаем что-то полезное break; default: // Что-то делаем >Иногда результат, полученный внутри case — это конец выполнения метода, содержащего switch . В таком случае его нужно как-то вернуть наружу. Для решения этой задачи есть два способа.
Первый способ — можно создать переменную перед switch , заполнить ее в case и затем вернуть значение этой переменной наружу:
class App < public static String getExplanation(int count) < // Объявляем переменную String result; // Заполняем switch(count) < case 1: result = "one"; break; case 2: result = "two"; break; default: result = null; >// Возвращаем return result; > >Второй способ проще и короче. Вместо создания переменной можно использовать case , внутри которого можно делать обычный возврат из метода. После return никакой код не выполняется, так что мы можем избавиться от break :
class App < public static String getExplanation(int count) < switch(count) < case 1: return "one"; case 2: return "two"; default: return null; >> >Switch хоть и встречается в коде, но технически всегда можно обойтись без него.
Польза этой конструкции в том, что она лучше выражает намерение программиста, когда нужно проверять конкретные значения переменной. В отличие от блоков else if , код со switch становится немного длиннее, но читать его гораздо проще.
Задание
Реализуйте метод getNumberExplanation() , который принимает на вход число и возвращает объяснение этого числа. Если для числа нет объяснения, то возвращается just a number . Объяснения есть только для следующих чисел:
- 666 — devil number
- 42 — answer for everything
- 7 — prime number
Примеры вызова функции:
App.getNumberExplanation(8); // just a number App.getNumberExplanation(666); // devil number App.getNumberExplanation(42); // answer for everything App.getNumberExplanation(7); // prime numberУпражнение не проходит проверку — что делать?
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя
Это нормально , в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Какие типы данных не могут быть в качестве условия switch-case; в java?
Какие могут:
char. byte, short, int, Character, Byte, Short, Integer или enum
Начиная с java 8 также может быть String.Остальные, соответственно, не могут.
Источник: Java language specification
Остальные ответы
Похожие вопросы
Ваш браузер устарелМы постоянно добавляем новый функционал в основной интерфейс проекта. К сожалению, старые браузеры не в состоянии качественно работать с современными программными продуктами. Для корректной работы используйте последние версии браузеров Chrome, Mozilla Firefox, Opera, Microsoft Edge или установите браузер Atom.
