Чому я не можу використовувати оператор переключення на рядку?


1004

Чи буде ця функціональність розміщена у більш пізній версії Java?

Чи може хтось пояснити, чому я не можу цього зробити, як в технічному способі switchроботи заяви Java ?


195
Це в SE 7. 16 років після його запиту. download.oracle.com/javase/tutorial/java/nutsandbolts/…
сердитийITguy

81
Sun був чесним у своїй оцінці: "Don't hold your breath."lol, bugs.sun.com/bugdatabase/view_bug.do?bug_id=1223179
raffian

3
@raffian Я думаю, що це тому, що вона зітхнула двічі. Вони трохи пізно затрималися і після майже 10 років. Тоді вона, можливо, пакувала коробки для обідів онукам.
WeirdElfB0y

Відповіді:


1003

Створення заяв про перемикання з Stringвипадками було впроваджено в Java SE 7 , принаймні, через 16 років після їх першого запиту. Чітка причина затримки не була надана, але це, швидше за все, було пов'язане з роботою.

Впровадження в JDK 7

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

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

Вимикачі в JVM

Для отримання більш детальної технічної глибини switchможна звернутися до специфікації JVM, де описана компіляція операторів комутації . У двох словах, є дві різні інструкції JVM, які можна використовувати для комутатора, залежно від рідкості констант, що використовуються у випадках. Обидва залежать від використання цілих констант для кожного випадку для ефективного виконання.

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

Якщо константи рідкі, виконується двійковий пошук правильного випадку - lookupswitchінструкція.

У знежиренні a 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;
}

26
Можливо, буде швидше просто використовувати If-Else-If замість хеша для рядкового перемикача. Я виявив, що словники є досить дорогими, коли зберігаються лише кілька предметів.
Джонатан Аллен

84
If-elseif-elseif-elseif-else може бути швидшим, але я б взяв чистіший код 99 разів із 100. Струни, будучи незмінними, кешують їх хеш-код, тому "обчислити" хеш швидко. Треба було б профайлювати код, щоб визначити, яка вигода від нього.
erickson

21
Причина, пов’язана з додаванням перемикача (String), полягає в тому, що він не відповідає гарантіям продуктивності, які очікує від операторів switch (). Вони не хотіли "вводити в оману" розробників. Чесно кажучи, я не думаю, що вони повинні гарантувати продуктивність перемикача () для початку.
Гілі

2
Якщо ви просто використовуєте Pillпевні дії на основі, strя б заперечував, якщо-else є кращим, оскільки це дозволяє обробляти strзначення за межами діапазону ЧЕРВНІ, СІНІ, без необхідності ловити винятки з valueOfабо вручну перевіряти на відповідність імені кожен тип перерахування, який просто додає зайві накладні витрати. З мого досвіду, це було сенс використовувати valueOfдля перетворення в перерахування, якщо пізніше було потрібно безпечне подання значення String.
MilesHampson

Цікаво, чи компілятори докладають будь-яких зусиль, щоб перевірити, чи є пара чисел (x, y), для яких набір значень (hash >> x) & ((1<<y)-1)дасть чіткі значення для кожного рядка, який hashCodeє різним, і (1<<y)менше, ніж удвічі більше рядків (або у принаймні, не набагато більше, ніж це).
supercat

125

Якщо у вашому коді є місце, де ви можете ввімкнути 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;
}

4
Ця методика також дозволяє вирішувати такі питання, як нечутливість до справи, псевдоніми тощо. Замість того, щоб залежати від дизайнера мови, придумати рішення "один розмір підходить усім".
Даррон

2
Погодьтеся з JeeBee, якщо ви перемикаєте струни, ймовірно, вам потрібен перерахунок. Рядок зазвичай представляє щось із інтерфейсом (користувачем чи іншим способом), що може чи не зміниться в майбутньому, тому краще замініть його на перерахунки
hhafez

18
Дивіться на xefer.com/2006/12/switchonstring для приємного написання цього методу.
Девід Шмітт

@DavidSchmitt Списання має один головний недолік. Він ловить усі винятки замість тих, які насправді кинуті методом.
М. Мімпен

91

Далі наведено повний приклад, заснований на публікації JeeBee, використовуючи java enum's замість користувацького методу.

Зауважте, що в 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
  }
}

26

Перемикачі на основі цілих чисел можна оптимізувати до дуже ефективного коду. Перемикачі на основі іншого типу даних можуть бути скомпільовані лише до серії операторів if ().

З цієї причини C & C ++ дозволяють перемикатися лише на цілі типи, оскільки для інших типів це було безглуздо.

Дизайнери C # вирішили, що стиль важливий, навіть якщо немає переваги.

Дизайнери Java, мабуть, думали, як дизайнери C.


26
Перемикачі, засновані на будь-якому об'єкті, що може бути заблокований, можуть бути реалізовані дуже ефективно за допомогою хеш-таблиці - див. .NET. Отже, ваша причина не зовсім коректна.
Конрад Рудольф

Так, і це те, чого я не розумію. Вони бояться, що хеш-об'єкти з часом стануть занадто дорогими?
Алекс Бердслі

3
@Nalandial: насправді, доклавши невеликих зусиль з боку компілятора, це зовсім не дорого, тому що коли набір рядків відомий, генерувати ідеальний хеш досить просто (хоча це не робиться .NET, хоча; певно, теж не вартих зусиль).
Конрад Рудольф

3
@Nalandial & @Konrad Rudolph - Хоча хеширование рядка (через його непорушну природу) здається вирішенням цієї проблеми, ви повинні пам’ятати, що всі нефінальні об’єкти можуть перекривати свої функції хешування. Це ускладнює час компіляції для забезпечення послідовності в комутаторі.
мартинаті

2
Ви також можете побудувати DFA для відповідності рядку (як це роблять двигуни регулярного вираження). Можливо, навіть ефективніше, ніж хеш.
Nate CK

19

Приклад прямого 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 & C ++ дозволяють перемикатися лише на цілі типи, оскільки це було безглуздо з іншими типами ".

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


Технічно регекси вже «перемикаються», оскільки вони в основному є лише державними машинами; вони мають лише два "справи", matchedі not matched. (Не беручи до уваги такі речі, як [названі] групи / тощо.)
JAB

1
docs.oracle.com/javase/7/docs/technotes/guides/language/… констатує: компілятор Java генерує, як правило, більш ефективний байт-код з операторів комутації, що використовують об'єкти String, ніж з ланцюгових операторів if-then-else.
Вім Деблауве

12

Окрім вищенаведених добрих аргументів, додам, що багато людей сьогодні сприймають switchяк застарілий залишок процедурного минулого Java (назад до часів С).

Я не повністю поділяю цю думку, я думаю, що switchможе бути корисним її в деяких випадках, принаймні через швидкість, і все одно, це краще, ніж деякі серії каскадних числових цифр, які else ifя бачив у якомусь коді ...

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


"Я колись шкодую, що ми не маємо (анонімні) функції як громадянина першого класу" Це вже не так.
користувач8397947

@dorukayhan Так, звичайно. Але ви хочете додати коментар до всіх відповідей за останні десять років, щоб сказати світові, що ми можемо їх мати, якщо оновити новіші версії Java? :-D
PhiLho

8

Якщо ви не використовуєте 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;
            }
        }
    }
}

5
Дві різні рядки можуть мати один і той же хеш-код, тому якщо ви ввімкнете хеш-коди, може бути взята помилкова гілка справи
Лій

@Lii Дякую, що вказали на це! Навряд чи це, але я б не вірив, що це працює. "FB" і "Ea" мають однаковий хеш-код, тому зіткнення знайти неможливо. Другий код, мабуть, більш надійний.
HyperNeutrino

Я здивований цій компіляції, тому що caseзаяви, як я вважав, завжди повинні бути постійними значеннями, і String.hashCode()не є такими (навіть якщо на практиці розрахунок ніколи не змінювався між JVM).
StaxMan

@StaxMan Гм цікаво, я ніколи не переставав це спостерігати. Але так, caseзначення операторів не повинні визначатися під час компіляції, щоб воно працювало чудово.
HyperNeutrino

4

Протягом багатьох років ми використовуємо для цього препроцесор (n open source).

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

Попередньо оброблені файли мають ім'я Foo.jpp і обробляються у Foo.java за допомогою сценарію мурашки.

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

Недоліки в тому, що ви не редагуєте Java, тому це трохи більше робочого процесу (редагувати, обробляти, компілювати / тестувати), а також IDE повернеться до Яви, яка трохи перекручена (перемикач перетворюється на ряд логічних кроків if / else) і порядок справи комутатора не підтримується.

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

Ви можете отримати його з SVN або переглянути код в Інтернеті . Вам знадобиться EBuild, щоб створити його таким, яким він є.


6
Для запуску коду за допомогою перемикача String вам не потрібен 1,7 JVM. Компілятор 1.7 перетворює перемикач String в те, що використовує раніше існуючий байт-код.
Dawood ibn Kareem

4

Інші відповіді сказали, що це було додано в Java 7 та дано вирішення попередніх версій. Ця відповідь намагається відповісти на "чому"

Java була реакцією на надскладні труднощі C ++. Він був розроблений як проста чиста мова.

String отримав трохи особливої ​​справи з мовою, але мені здається зрозумілим, що дизайнери намагалися звести кількість спеціального корпусу та синтаксичного цукру до мінімуму.

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

Між 1,0 та 1,4 мова сама залишалася майже однаковою. Більшість удосконалень Java були на стороні бібліотеки.

Все змінилося з Java 5, мова істотно розширилася. Подальші розширення дотримуються у версіях 7 та 8. Я очікую, що ця зміна ставлення була зумовлена ​​підвищенням C #


Оповідання про перемикач (String) відповідає історії, часовій шкалі, контексту cpp / cs.
Еспресо

Була великою помилкою не реалізовувати цю функцію, все інше - дешевий привід, який Java втрачала багатьма користувачами протягом багатьох років через відсутність прогресу та впертість дизайнерів не розвивати мову. На щастя, вони повністю змінили напрямок та відношення після JDK7
firephil

0

JEP 354: Перемикання виразів (попередній перегляд) в JDK-13 та JEP 361: Вирази комутації (стандартні) в JDK-14 розширить оператор перемикання, щоб його можна було використовувати як вираз .

Тепер ти можеш:

  • безпосередньо призначити змінну з виразу перемикача ,
  • використовувати нову форму мітки перемикача ( case L ->):

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

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

    Щоб отримати значення з виразу перемикача, 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");
    }

-2

Не дуже красиво, але ось інший спосіб для 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 вітер; Я вставляю бурхливу банку і створюю groovyклас утиліти, щоб виконувати всі ці речі та багато іншого, що мені здається допитливим зробити на Java (оскільки я застряг, використовуючи Java 6 на підприємстві.)

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

9
@SSpoke Оскільки це питання Java, а відповідь Groovy - це поза темою і марною плагією.
Мартін

12
Навіть у консервативних великих SW будинках Groovy використовується поряд з Java. JVM надає мовному агностичному середовищу більше, ніж мова зараз, щоб змішати та використовувати найрелевантнішу парадигму програмування для рішення. Тож, можливо, зараз я повинен додати фрагмент у Clojure, щоб зібрати більше знімків :) ...
Алекс Пуннен

1
Також, як працює синтаксис? Я здогадуюсь, що Groovy - це інша мова програмування ...? Вибачте. Я нічого не знаю про Groovy.
HyperNeutrino

-4

Коли ви використовуєте intellij, також дивіться на:

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

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

Якщо у вас є кілька модулів, переконайтеся, що ви встановили правильний рівень мови на вкладці модуля.


1
Не впевнений, наскільки ваша відповідь відповідає питанню. Він запитав, чому оператори переключення рядків на зразок наступних недоступні: String mystring = "щось"; switch (mystring) {case "something" 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.
HyperNeutrino
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.