Почему я не могу использовать оператор switch в строке?

Будет ли эта функциональность реализована в более поздней версии Java?

Может ли кто-нибудь объяснить, почему я не могу этого сделать, например, технический способ работы оператора switch в Java?


person Alex Beardsley    schedule 03.12.2008    source источник
comment
@raffian Я думаю, это потому, что она дважды вздохнула. Они тоже немного опоздали с ответом, спустя почти 10 лет. Тогда она могла бы упаковывать ланч-боксы своим внукам.   -  person WeirdElfB0y    schedule 26.08.2016


Ответы (17)


Операторы переключения с String случаями были реализованы в Java SE 7 в течение как минимум 16 лет после их первого запроса. Четкой причины задержки не было указано, но, скорее всего, это было связано с производительностью.

Реализация в JDK 7

Эта функция теперь реализована в javac процессе "удаления сахара"; чистый , высокоуровневый синтаксис с использованием String констант в case объявлениях расширяется во время компиляции в более сложный код, следующий шаблону. Результирующий код использует инструкции JVM, которые существовали всегда.

switch с String регистрами преобразуется в два переключателя во время компиляции. Первый отображает каждую строку в уникальное целое число в исходном переключателе. Это делается путем первого включения хэш-кода метки. Соответствующий случай - это оператор if, который проверяет равенство строк; если в хэше есть коллизии, тест будет каскадным if-else-if. Второй переключатель отражает это в исходном исходном коде, но заменяет метки case их соответствующими позициями. Этот двухэтапный процесс позволяет легко сохранить управление потоком исходного коммутатора.

Коммутаторы в JVM

Для получения дополнительной технической информации о switch вы можете обратиться к Спецификации JVM, где описывается компиляция операторов switch. Вкратце, есть две разные инструкции JVM, которые можно использовать для переключения, в зависимости от разреженности констант, используемых в случаях. Оба зависят от использования целочисленных констант для каждого случая для эффективного выполнения.

Если константы плотные, они используются в качестве индекса (после вычитания наименьшего значения) в таблице указателей инструкций инструкции tableswitch.

Если константы редкие, выполняется двоичный поиск правильного регистра с помощью инструкции lookupswitch.

При удалении сахара switch на String объекты, вероятно, будут использоваться обе инструкции. lookupswitch подходит для первого включения хэш-кодов, чтобы найти исходное положение корпуса. Полученный порядковый номер естественным образом подходит для tableswitch.

Обе инструкции требуют, чтобы целочисленные константы, присвоенные каждому случаю, были отсортированы во время компиляции. Во время выполнения, хотя O(1) производительность tableswitch в целом кажется лучше, чем O(log(n)) производительность lookupswitch, требуется некоторый анализ, чтобы определить, достаточно ли плотна таблица, чтобы оправдать компромисс между пространством и временем. Билл Веннерс написал отличную статью, в которой этот вопрос рассматривается более подробно, а также -hood посмотрите другие инструкции управления потоком Java.

Перед JDK 7

До JDK 7 enum мог приблизительно соответствовать коммутатору на основе String. Здесь используется статический _22 _ метод, сгенерированный компилятором для каждого enum типа. Например:

Pill p = Pill.valueOf(str);
switch(p) {
  case RED:  pop();  break;
  case BLUE: push(); break;
}
person erickson    schedule 03.12.2008
comment
Возможно, будет быстрее просто использовать If-Else-If вместо хеша для переключателя на основе строки. Я обнаружил, что словари довольно дороги, если хранить только несколько элементов. - person Jonathan Allen; 05.12.2008
comment
If-elseif-elseif-elseif-else может быть быстрее, но я бы взял более чистый код 99 раз из 100. Строки, будучи неизменными, кэшируют свой хэш-код, поэтому вычисление хеша происходит быстро. Чтобы определить, в чем выгода, нужно профилировать код. - person erickson; 05.12.2008
comment
Причина, по которой не следует добавлять switch (String), заключается в том, что это не соответствует гарантиям производительности, ожидаемым от операторов switch (). Они не хотели вводить разработчиков в заблуждение. Честно говоря, я не думаю, что они должны изначально гарантировать производительность switch (). - person Gili; 23.12.2008
comment
Если вы просто используете Pill для выполнения некоторых действий на основе str, я бы сказал, что предпочтительнее if-else, поскольку он позволяет обрабатывать str значения за пределами диапазона КРАСНОГО, СИНЕГО без необходимости перехватывать исключение из valueOf или вручную проверять совпадение против имени каждого типа перечисления, что просто добавляет ненужные накладные расходы. По моему опыту, использование valueOf для преобразования в перечисление имело смысл только в том случае, если позже потребовалось безопасное для типов представление значения String. - person MilesHampson; 23.07.2013
comment
Интересно, прилагают ли компиляторы какие-либо усилия, чтобы проверить, существует ли какая-либо пара чисел (x, y), для которой набор значений (hash >> x) & ((1<<y)-1) даст разные значения для каждой строки, у которой hashCode отличается, а (1<<y) меньше, чем в два раза, количество строк (или, по крайней мере, не намного больше). - person supercat; 18.12.2013
comment
Быстрее ли if-else-if для строк? - person fernal73; 20.12.2020
comment
@ fernal73 Это зависит от того, сколько if вы каскадировали, и был ли уже вычислен хэш-код строки переключения. Для двоих или троих это могло бы быть быстрее. Однако в какой-то момент оператор switch, вероятно, будет работать лучше. Что еще более важно, во многих случаях оператор switch, вероятно, более читабелен. - person erickson; 21.12.2020
comment
Думаю, разница незначительная. - person fernal73; 21.12.2020
comment
raw.githubusercontent.com/Fernal73/LearnJava/master/JMH/ jmh /. Тестовый режим Cnt Score Ошибка Единицы SwitchString.ifElseRandom thrpt 25 11245993.080 ± 13505,859 операций / с SwitchString.ifElseRandomHash thrpt 25 14610552,723 ± 9316,162 операций / с SwitchString.switchCaseRandom thrpt 25 11360947,624 с / с - person fernal73; 21.12.2020

Если у вас есть место в вашем коде, где вы можете включить String, тогда может быть лучше реорганизовать String, чтобы он был перечислением возможных значений, которые вы можете включить. Конечно, вы ограничиваете потенциальные значения String, которые вы можете иметь, значениями в перечислении, что может быть, а может и не быть желательным.

Конечно, ваше перечисление может иметь запись для 'other' и метод fromString (String), тогда у вас может быть

ValueEnum enumval = ValueEnum.fromString(myString);
switch (enumval) {
   case MILK: lap(); break;
   case WATER: sip(); break;
   case BEER: quaff(); break;
   case OTHER: 
   default: dance(); break;
}
person JeeBee    schedule 03.12.2008
comment
Этот метод также позволяет вам решать такие проблемы, как нечувствительность к регистру, псевдонимы и т. Д. Вместо того, чтобы полагаться на разработчика языка, чтобы придумать универсальное решение. - person Darron; 04.12.2008
comment
Согласитесь с JeeBee, если вы включаете строки, вероятно, потребуется перечисление. Строка обычно представляет собой что-то, поступающее в интерфейс (пользовательский или иной), что может измениться или не измениться в будущем, поэтому лучше замените его перечислениями. - person hhafez; 04.12.2008
comment
См. xefer.com/2006/12/switchonstring, чтобы получить хорошее описание этого метод. - person David Schmitt; 14.04.2010
comment
@DavidSchmitt В описании есть один серьезный недостаток. Он перехватывает все исключения вместо тех, которые фактически выбрасываются методом. - person M. Mimpen; 04.02.2014

Ниже приведен полный пример, основанный на сообщении JeeBee, с использованием перечисления java вместо использования настраиваемого метода.

Обратите внимание, что в Java SE 7 и более поздних версиях вы можете вместо этого использовать объект String в выражении оператора switch.

public class Main {

    /**
    * @param args the command line arguments
    */
    public static void main(String[] args) {

      String current = args[0];
      Days currentDay = Days.valueOf(current.toUpperCase());

      switch (currentDay) {
          case MONDAY:
          case TUESDAY:
          case WEDNESDAY:
              System.out.println("boring");
              break;
          case THURSDAY:
              System.out.println("getting better");
          case FRIDAY:
          case SATURDAY:
          case SUNDAY:
              System.out.println("much better");
              break;

      }
  }

  public enum Days {

    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
  }
}
person Thulani Chivandikwa    schedule 16.09.2011

Переключатели, основанные на целых числах, можно оптимизировать для получения очень эффективного кода. Переключатели, основанные на другом типе данных, могут быть скомпилированы только в серию операторов if ().

По этой причине C и C ++ допускают переключение только на целочисленные типы, поскольку с другими типами это было бессмысленно.

Разработчики C # решили, что стиль важен, даже если в нем нет преимущества.

Создатели Java, по-видимому, думали, как дизайнеры C.

person James Curran    schedule 03.12.2008
comment
Переключатели, основанные на любом хешируемом объекте, могут быть очень эффективно реализованы с использованием хэш-таблицы - см. .NET. Итак, ваша причина не совсем верна. - person Konrad Rudolph; 03.12.2008
comment
Да, и вот этого я не понимаю. Боятся ли они, что хэширующие объекты в конечном итоге станут слишком дорогими? - person Alex Beardsley; 03.12.2008
comment
@Nalandial: на самом деле, с небольшими усилиями со стороны компилятора, это совсем не дорого, потому что, когда известен набор строк, довольно легко сгенерировать идеальный хеш (хотя .NET не делает этого; наверное, тоже не стоит усилий). - person Konrad Rudolph; 03.12.2008
comment
@Nalandial и @Konrad Rudolph - при хешировании строки (из-за ее неизменной природы) кажется решением этой проблемы, вы должны помнить, что все неокончательные объекты могут иметь переопределенные функции хеширования. Это затрудняет во время компиляции обеспечение согласованности в переключателе. - person martinatime; 04.12.2008
comment
Вы также можете создать DFA для соответствия строке (как это делают механизмы регулярных выражений). Возможно, даже более эффективно, чем хеширование. - person Nate C-K; 22.08.2011
comment
@Konrad & Nate: В то время как хеш-таблица или конечный автомат будут работать в коммутаторе, потребуется чертовски много элементов case для преодоления внутренних накладных расходов любого из них. Мое безумное предположение состоит в том, что 80% всех коммутаторов имеют менее 10 случаев, а 99,9% имеют менее 20. При таких размерах было бы очень сложно победить цепочку if () для скорости. - person James Curran; 24.08.2011
comment
Еще одна вещь, о которой следует упомянуть, это то, что байт-код Java имеет прямую поддержку переключения значений int; поэтому существует собственная реализация для эффективного переключения констант типа int (включая Enums, где можно использовать ordinal ()). - person StaxMan; 05.10.2011
comment
Каким образом внутренние накладные расходы конечного автомата (который сам может быть реализован в виде серии каскадных переключателей с каждым последующим символом в строке в качестве включаемого значения) были бы больше, чем у связанных if () s с точки зрения скорости? В худшем случае мне кажется, что они будут примерно равны, поскольку связанные if будут выполнять сравнение строк каждый раз, если не будет выполнена какая-то оптимизация, и в лучшем случае FSM-as-cascaded-Switches фактически быть более эффективным с точки зрения количества выполняемых операций. - person JAB; 30.07.2012
comment
Конечно, это не принимает во внимание накладные расходы памяти и то, как на скорость будут влиять проблемы с кэшированием и различными другими аспектами конвейерной обработки процессора и т. Д., А также тот факт, что худший и лучший сценарии для двух алгоритмы могут отличаться, а могут и не отличаться в зависимости от того, какие строки проверяются и как они упорядочены ... (я думаю, лучше всего будет амортизироваться в лучшем / худшем случае?) - person JAB; 30.07.2012
comment
То, что вы думаете, что в этом нет никакого преимущества, на самом деле не означает, что это бесполезно. Смею вас спросить любого разработчика Python о многострочных комментариях, они все, вероятно, скажут вам, что это бесполезно. Они скажут вам, что если вы действительно хотите, чтобы они использовали строки документации, даже если строки документов загружаются в память (по умолчанию). - person Natalie Adams; 10.12.2012
comment
Загрузка строк документации в память способствует самоанализу. Вы не только знаете имена и подписи функций, но и имеете их документацию. Отлично подходит для интерактивного или для создания IDE. - person Jonathan Baldwin; 12.10.2013

Джеймс Карран кратко говорит: «Переключатели, основанные на целых числах, могут быть оптимизированы для получения очень эффективного кода. Переключатели, основанные на другом типе данных, могут быть скомпилированы только в серию операторов if (). По этой причине C и C ++ допускают переключение только на целочисленные типы, поскольку с другими типами это было бессмысленно ".

Мое мнение, и оно только так, заключается в том, что как только вы начинаете включать непримитивы, вам нужно думать о «равных» и «==». Во-первых, сравнение двух строк может быть довольно длительной процедурой, что усугубляет проблемы с производительностью, упомянутые выше. Во-вторых, если есть переключение строк, будет требоваться включение строк без учета регистра, включение строк с учетом / игнорирование языкового стандарта, переключение строк на основе регулярного выражения .... Я бы одобрил решение, которое сэкономило много времени для языковых разработчиков за счет небольшого количества времени для программистов.

person DJClayworth    schedule 03.12.2008
comment
Технически регулярные выражения уже переключаются, поскольку они в основном являются конечными автоматами; у них просто есть только два случая, matched и not matched. (Однако не принимая во внимание такие вещи, как [named] groups / и т. Д.) - person JAB; 30.07.2012
comment
docs.oracle.com/javase/7/ docs / technotes / guides / language / утверждает: Компилятор Java обычно генерирует более эффективный байт-код из операторов switch, использующих объекты String, чем из связанных операторов if-then-else. - person Wim Deblauwe; 14.07.2016

Также может быть показан пример прямого использования String начиная с версии 1.7:

public static void main(String[] args) {

    switch (args[0]) {
        case "Monday":
        case "Tuesday":
        case "Wednesday":
            System.out.println("boring");
            break;
        case "Thursday":
            System.out.println("getting better");
        case "Friday":
        case "Saturday":
        case "Sunday":
            System.out.println("much better");
            break;
    }

}
person Gunnar Forsgren - Mobimation    schedule 09.04.2015

Помимо приведенных выше хороших аргументов, я добавлю, что многие люди сегодня видят switch как устаревший остаток процедурного прошлого Java (назад во времена C).

Я не полностью разделяю это мнение, я думаю, что switch может быть полезным в некоторых случаях, по крайней мере, из-за его скорости, и в любом случае это лучше, чем некоторые серии каскадных числовых else if, которые я видел в каком-то коде ...

Но действительно, стоит посмотреть на случай, когда вам нужен переключатель, и посмотреть, нельзя ли его заменить чем-то более OO. Например, перечисления в Java 1.5+, возможно, HashTable или какая-то другая коллекция (иногда я сожалею, что у нас нет (анонимных) функций в качестве первоклассного гражданина, как в Lua - у которого нет переключателя - или JavaScript) или даже полиморфизма.

person PhiLho    schedule 03.12.2008
comment
иногда я сожалею, что у нас нет (анонимных) функций в качестве первоклассного гражданина Это уже не так. - person dorukayhan; 18.01.2017
comment
@dorukayhan Да, конечно. Но не хотите ли вы добавить комментарий ко всем ответам за последние десять лет, чтобы сообщить миру, что они могут быть получены, если мы обновимся до более новых версий Java? :-D - person PhiLho; 24.01.2017

Если вы не используете JDK7 или выше, вы можете использовать hashCode() для имитации. Поскольку String.hashCode() обычно возвращает разные значения для разных строк и всегда возвращает одинаковые значения для одинаковых строк, это довольно надежно (разные строки могут создавать тот же хэш-код, что и @Lii, упомянутый в комментарии, например "FB" и "Ea") См. документация.

Итак, код будет выглядеть так:

String s = "<Your String>";

switch(s.hashCode()) {
case "Hello".hashCode(): break;
case "Goodbye".hashCode(): break;
}

Таким образом, вы технически включаете int.

В качестве альтернативы вы можете использовать следующий код:

public final class Switch<T> {
    private final HashMap<T, Runnable> cases = new HashMap<T, Runnable>(0);

    public void addCase(T object, Runnable action) {
        this.cases.put(object, action);
    }

    public void SWITCH(T object) {
        for (T t : this.cases.keySet()) {
            if (object.equals(t)) { // This means that the class works with any object!
                this.cases.get(t).run();
                break;
            }
        }
    }
}
person hyper-neutrino    schedule 23.05.2015
comment
Две разные строки могут иметь один и тот же хэш-код, поэтому, если вы включите хэш-коды, может быть выбрана неправильная ветвь case. - person Lii; 23.01.2016
comment
@Lii Спасибо, что указали на это! Хотя это маловероятно, но я бы не поверил, что это сработает. FB и Ea имеют одинаковый хэш-код, поэтому найти коллизию не исключено. Второй код, наверное, более надежен. - person hyper-neutrino; 26.01.2016
comment
Я удивлен, что эта компиляция компилируется, поскольку операторы case, как я думал, всегда должны быть постоянными значениями, а String.hashCode() не таковыми (даже если на практике расчет между JVM никогда не менялся). - person StaxMan; 26.01.2018
comment
@StaxMan Хм, интересно, я никогда не переставал это наблюдать. Но да, значения оператора case не обязательно должны определяться во время компиляции, поэтому он работает нормально. - person hyper-neutrino; 26.01.2018

В течение многих лет мы использовали для этого препроцессор с открытым исходным кодом.

//#switch(target)
case "foo": code;
//#end

Предварительно обработанные файлы называются Foo.jpp и обрабатываются в Foo.java с помощью ant-скрипта.

Преимущество в том, что он переработан в Java, работающую на 1.0 (хотя обычно мы поддерживали только обратно до 1.4). К тому же это было намного проще (много переключателей строк), чем придумывать перечисления или другие обходные пути - код было намного легче читать, поддерживать и понимать. IIRC (на данный момент не может предоставить статистику или технические рассуждения), он также был быстрее, чем естественные эквиваленты Java.

Недостатки в том, что вы не редактируете Java, поэтому это немного больше рабочего процесса (редактирование, обработка, компиляция / тестирование), плюс IDE будет связываться с Java, которая немного запутана (переключатель становится серией логических шагов if / else) и порядок корпуса переключателя не сохраняется.

Я бы не рекомендовал его для версии 1.7+, но он полезен, если вы хотите запрограммировать Java, ориентированную на более ранние JVM (поскольку Joe public редко устанавливает последнюю версию).

Вы можете получить его из SVN или просмотрите код в Интернете. Вам понадобится EBuild, чтобы собрать его как есть.

person Charles Goodwin    schedule 15.11.2013
comment
Для запуска кода с переключателем String вам не нужна JVM 1.7. Компилятор 1.7 превращает переключатель String во что-то, что использует ранее существовавший байтовый код. - person Dawood ibn Kareem; 04.12.2013

В других ответах говорится, что это было добавлено в Java 7 и даны обходные пути для более ранних версий. Этот ответ пытается ответить на вопрос «почему».

Java была реакцией на чрезмерную сложность C ++. Он был разработан как простой чистый язык.

Строка получила небольшую обработку особых случаев в языке, но мне кажется очевидным, что дизайнеры пытались свести к минимуму количество специального корпуса и синтаксического сахара.

включение строк довольно сложно, поскольку строки не являются простыми примитивными типами. В то время, когда разрабатывалась Java, это не было обычным явлением и не очень хорошо вписывалось в минималистский дизайн. Тем более, что они решили не использовать специальный случай == для строк, было бы (и есть) немного странно, если бы случай работал там, где == не работает.

Между 1.0 и 1.4 сам язык практически не изменился. Большинство улучшений Java касались библиотеки.

Все изменилось с выходом Java 5, язык был существенно расширен. Дальнейшие расширения последовали в версиях 7 и 8. Я ожидаю, что это изменение отношения было вызвано появлением C #.

person plugwash    schedule 19.01.2017
comment
Рассказ о переключателе (String) соответствует истории, временной шкале, контексту cpp / cs. - person Espresso; 04.04.2018
comment
Не реализовывать эту функцию было большой ошибкой, все остальное - дешевое оправдание. За эти годы Java потеряла многих пользователей из-за отсутствия прогресса и упрямства дизайнеров, не желающих развивать язык. К счастью, они полностью изменили направление и отношение после JDK7. - person firephil; 10.03.2019

Технические детали были хорошо объяснены в этом ответе. Я просто хотел добавить это с помощью выражений переключателя Java 12 вы можете сделать это со следующим синтаксисом:

String translation(String cat_language) {
    return switch (cat_language) {
        case "miau miau" -> "I am to run";
        case "miauuuh" -> "I am to sleep";
        case "mi...au?" ->  "leave me alone";
        default ->  "eat";
    };
} 
person dreamcrash    schedule 16.03.2021

JEP 354: переключение выражений (предварительная версия) в JDK-13 и JEP 361: переключение выражений (стандартное) в JDK-14 будет расширьте оператор switch, чтобы его можно было использовать как выражение.

Теперь вы можете:

  • напрямую назначить переменную из выражения переключения,
  • использовать новую форму метки переключателя (case L ->):
    # P3 #
  • используйте несколько констант для каждого случая, разделенных запятыми,
  • а также больше нет значения break:
    # P4 #

Итак, демонстрация из ответов (1, 2) может выглядеть так:

  public static void main(String[] args) {
    switch (args[0]) {
      case "Monday", "Tuesday", "Wednesday" ->  System.out.println("boring");
      case "Thursday" -> System.out.println("getting better");
      case "Friday", "Saturday", "Sunday" -> System.out.println("much better");
    }
person Iskuskov Alexander    schedule 09.02.2020

В Java 11+ это возможно и с переменными. Единственное условие - он должен быть постоянным.

Например:

final String LEFT = "left";
final String RIGHT = "right";
final String UP = "up";
final String DOWN = "down";

String var = ...;

switch (var) {
    case LEFT:
    case RIGHT:
    case DOWN:
    default:
        return 0;
}

PS. Я не пробовал этого с более ранними jdks. Поэтому обновите ответ, если он там тоже поддерживается.

person Imtiaz Shakil Siddique    schedule 30.06.2020
comment
информация: метки должны быть постоянными выражениями, начиная с версии 7: JLS 14.11 - person ; 16.03.2021

Не очень красиво, но вот другой способ для Java 6 и ниже:

String runFct = 
        queryType.equals("eq") ? "method1":
        queryType.equals("L_L")? "method2":
        queryType.equals("L_R")? "method3":
        queryType.equals("L_LR")? "method4":
            "method5";
Method m = this.getClass().getMethod(runFct);
m.invoke(this);
person Conete Cristian    schedule 01.06.2017

В Groovy это совсем несложно; Я встраиваю отличную банку и создаю служебный класс groovy, чтобы делать все эти и многое другое, что мне неприятно делать в Java (поскольку я застрял на Java 6 на предприятии).

it.'p'.each{
switch ([email protected]()){
   case "choclate":
     myholder.myval=(it.text());
     break;
     }}...
person Alex Punnen    schedule 29.11.2012
comment
@SSpoke Потому что это вопрос Java, а ответ Groovy не по теме и бесполезный плагин. - person Martin; 11.04.2014
comment
Даже в консервативных крупных компаниях программного обеспечения Groovy используется вместе с Java. JVM дает языковой независимой среде больше, чем язык, чтобы смешивать и использовать наиболее подходящую парадигму программирования для решения. Так что, возможно, теперь мне стоит добавить фрагмент в Clojure, чтобы собрать больше голосов против :) ... - person Alex Punnen; 22.05.2014
comment
Кроме того, как работает синтаксис? Я предполагаю, что Groovy - это другой язык программирования ...? Извините. Я ничего не знаю о Groovy. - person hyper-neutrino; 26.01.2016

Когда вы используете intellij, посмотрите также:

Файл -> Структура проекта -> Проект

Файл -> Структура проекта -> Модули

Если у вас несколько модулей, убедитесь, что вы установили правильный уровень языка на вкладке модулей.

person botenvouwer    schedule 06.01.2016
comment
Не уверен, насколько ваш ответ имеет отношение к вопросу. Он спросил, почему недоступны следующие операторы переключения строк: String mystring = something; switch (mystring) {case something sysout (здесь); . . } - person Deepak Agarwal; 15.11.2016

person    schedule
comment
OP не спрашивает, как включить строку. Он / она спрашивает, почему он / она не может этого сделать из-за ограничений синтаксиса до JDK7. - person hyper-neutrino; 02.07.2015