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

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

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

17 ответов

Решение

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

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

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

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

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

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

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

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

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

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

До 7 JDK

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

Pill p = Pill.valueOf(str);
switch(p) {
  case RED:  pop();  break;
  case BLUE: push(); break;
}

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

Конечно, ваше перечисление может иметь запись для "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;
}

Ниже приведен полный пример, основанный на публикации 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
  }
}

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

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

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

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

Пример прямого 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;
    }

}

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

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

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

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

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

Если вы не используете 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;
            }
        }
    }
}

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

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

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

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

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

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

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

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

В этом ответе подробно описаны технические детали . Я просто хотел добавить, что с выражениями переключения 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";
    };
} 

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

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

  • напрямую назначить переменную из выражения переключателя,
  • использовать новую форму метки переключателя (case L ->):

    Код справа от метки переключателя "case L ->" может быть выражением, блоком или (для удобства) оператором throw.

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

    Чтобы получить значение из выражения переключения, break со значением опускается в пользу yield заявление.

Таким образом, демонстрация из ответов ( 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");
    }

В 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. Пожалуйста, обновите ответ, если он там тоже поддерживается.

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

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

it.'p'.each{
switch (it.@name.text()){
   case "choclate":
     myholder.myval=(it.text());
     break;
     }}...

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

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

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

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

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; 
         } 
    } 

} 
Другие вопросы по тегам