Почему я не могу использовать оператор switch для String? - PullRequest
963 голосов
/ 03 декабря 2008

Эта функция будет добавлена ​​в более позднюю версию Java?

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

Ответы [ 14 ]

977 голосов
/ 03 декабря 2008

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

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

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

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

Переключатели в JVM

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

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

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

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

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

До JDK 7

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

Pill p = Pill.valueOf(str);
switch(p) {
  case RED:  pop();  break;
  case BLUE: push(); break;
}
120 голосов
/ 03 декабря 2008

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

Конечно, в вашем перечислении может быть запись для «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;
}
89 голосов
/ 16 сентября 2011

Ниже приведен полный пример, основанный на посте 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
  }
}
25 голосов
/ 03 декабря 2008

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

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

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

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

18 голосов
/ 09 апреля 2015

Пример прямого использования 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 голосов
/ 03 декабря 2008

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

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

12 голосов
/ 04 декабря 2008

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

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

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

8 голосов
/ 23 мая 2015

Если вы не используете 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;
            }
        }
    }
}
4 голосов
/ 19 января 2017

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

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

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

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

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

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

4 голосов
/ 15 ноября 2013

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

//#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 , чтобы построить его как есть.

...