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

885

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

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

  • 187
    Это в SE 7. 16 лет после его запроса. download.oracle.com/javase/tutorial/java/nutsandbolts/...
  • 77
    Сунь был честен в своих оценках: "Don't hold your breath." lol, bugs.sun.com/bugdatabase/view_bug.do?bug_id=1223179
Показать ещё 1 комментарий
Теги:
string
switch-statement

14 ответов

961
Лучший ответ

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

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

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

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

Переключение в JVM

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

До JDK 7

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

Pill p = Pill.valueOf(str);
switch(p) {
  case RED:  pop();  break;
  case BLUE: push(); break;
}
  • 24
    Возможно, быстрее будет просто использовать If-Else-If вместо хеша для строкового переключателя. Я нашел словари довольно дорогими, если хранить только несколько предметов.
  • 78
    If-elseif-elseif-elseif-else может быть быстрее, но я бы выбрал более чистый код из 99 раз из 100. Строки, будучи неизменяемыми, кэшируют свой хэш-код, поэтому «вычисление» хеша происходит быстро. Нужно было бы профилировать код, чтобы определить, какая выгода есть.
Показать ещё 3 комментария
120

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

Конечно, в вашем перечислении может быть запись для "другого" и метода 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;
}
  • 3
    Этот метод также позволяет вам решать такие проблемы, как отсутствие учета регистра, псевдонимы и т. Д. Вместо того, чтобы полагаться на то, что разработчик языка придумает решение «один размер подходит всем».
  • 1
    Согласитесь с JeeBee, если вы включаете строки, вероятно, нужен enum. Строка обычно представляет собой что-то, идущее в интерфейс (пользовательское или иное), которое может измениться или не измениться в будущем, поэтому лучше заменить его на enums
Показать ещё 2 комментария
90

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

Обратите внимание, что в 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
  }
}
27

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

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

Дизайнеры С# решили, что стиль важен, даже если не было никакого преимущества.

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

  • 24
    Переключатели, основанные на любом хешируемом объекте, могут быть очень эффективно реализованы с использованием хеш-таблицы - см. .NET. Так что твоя причина не совсем верна.
  • 0
    Да, и это то, чего я не понимаю. Они боятся, что хеширование объектов в конечном итоге станет слишком дорогим?
Показать ещё 9 комментариев
18

Пример прямого использования 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;
    }

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

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

  • 0
    Технически, регулярные выражения уже «переключаются», так как они в основном просто конечные автоматы; у них просто есть только два «случая», matched и not matched . (Однако, не принимая во внимание такие вещи, как [named] groups / etc.)
  • 1
    docs.oracle.com/javase/7/docs/technotes/guides/language/… состояния: компилятор Java обычно генерирует более эффективный байт-код из операторов switch, которые используют объекты String, чем из цепочечных операторов if-then-else.
12

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

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

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

  • 0
    «Иногда я сожалею, что у нас нет (анонимных) функций как гражданина первого класса» Это больше не так.
  • 0
    @dorukayhan Да, конечно. Но хотите ли вы добавить комментарий ко всем ответам за последние десять лет, чтобы сообщить миру, что мы можем получить их, если мы обновимся до более новых версий Java? :-D
8

Если вы не используете JDK7 или выше, вы можете использовать hashCode() для имитации. Поскольку String.hashCode() обычно возвращает разные значения для разных строк и всегда возвращает равные значения для равных строк, он достаточно надежный (разные строки могут выдавать один и тот же хеш-код как @Lii, упомянутый в комментарии, например, "FB" и "Ea" "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;
            }
        }
    }
}
  • 5
    Две разные строки могут иметь один и тот же хеш-код, поэтому, если вы включите хеш-коды, может быть взята неверная ветвь регистра.
  • 0
    @Lii Спасибо за указание на это! Хотя это маловероятно, но я бы не поверил, что это сработает. «FB» и «Ea» имеют одинаковый хэш-код, поэтому найти коллизию невозможно. Второй код, вероятно, более надежный.
Показать ещё 2 комментария
4

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

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

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

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

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

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

  • 0
    Повествование о switch (String) соответствует истории, временной шкале, контексту cpp / cs.
  • 0
    Не реализовывать эту функцию было большой ошибкой, все остальное - дешевое оправдание. За эти годы Java потеряла многих пользователей из-за отсутствия прогресса и упрямства дизайнеров не развивать язык. К счастью, они полностью изменили направление и отношение после JDK7
4

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

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

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

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

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

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

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

  • 6
    Вам не нужна 1.7 JVM для запуска кода с переключателем String. Компилятор 1.7 превращает переключатель String в нечто, использующее ранее существующий байт-код.
0

Не очень красиво, но вот еще один способ для 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);
-3

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

it.'p'.each{
switch ([email protected]()){
   case "choclate":
     myholder.myval=(it.text());
     break;
     }}...
  • 4
    почему у этого парня отрицательные голоса?
  • 9
    @SSpoke Потому что это вопрос Java, а ответ на Groovy не по теме и бесполезный плагин.
Показать ещё 2 комментария
-4

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

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

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

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

  • 0
    Почему я проголосовал, пожалуйста, оставьте отзыв!
  • 1
    Не уверен, насколько ваш ответ имеет отношение к вопросу. Он спросил, почему операторы строкового переключателя, подобные следующим, недоступны: String mystring = "кое-что"; switch (mystring) {case "что-то" sysout ("попал сюда"); , , }
-8
public class StringSwitchCase { 

    public static void main(String args[]) {

        visitIsland("Santorini"); 
        visitIsland("Crete"); 
        visitIsland("Paros"); 

    } 

    public static void visitIsland(String island) {
         switch(island) {
          case "Corfu": 
               System.out.println("User wants to visit Corfu");
               break; 
          case "Crete": 
               System.out.println("User wants to visit Crete");
               break; 
          case "Santorini": 
               System.out.println("User wants to visit Santorini");
               break; 
          case "Mykonos": 
               System.out.println("User wants to visit Mykonos");
               break; 
         default: 
               System.out.println("Unknown Island");
               break; 
         } 
    } 

} 
  • 8
    ОП не спрашивает, как включить строку. Он / она спрашивает, почему он / она не может этого сделать из-за ограничений синтаксиса до JDK7.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню