Чому оператор String switch не підтримує нульовий регістр?


125

Мені просто цікаво, чому switchзаява Java 7 не підтримує nullвипадок і замість цього кидає NullPointerException? Дивіться коментований рядок нижче (приклад, взятий із статті навчальних посібників Javaswitch ):

{
    String month = null;
    switch (month) {
        case "january":
            monthNumber = 1;
            break;
        case "february":
            monthNumber = 2;
            break;
        case "march":
            monthNumber = 3;
            break;
        //case null:
        default: 
            monthNumber = 0;
            break;
    }

    return monthNumber;
}

Це дозволило б уникнути ifумови перевірки нуля перед кожним switchвикористанням.


12
На це немає жодної переконливої ​​відповіді, оскільки ми не люди, які створили мову. Усі відповіді будуть чистою догадкою.
asteri

2
Спроба увімкнути nullспричинить виняток. Зробіть ifперевірку на предмет null, а потім перейдіть до switchзаяви.
gparyani

28
З JLS : На думку дизайнерів мови програмування Java, [кидання a, NullPointerExceptionякщо вираз оцінюється nullна час виконання] є кращим результатом, ніж мовчки пропустити всю операцію перемикання або вибрати виконання операторів (якщо такі є) після мітка за замовчуванням (якщо така є).
gparyani

3
@gparyani: Зробіть так, що відповідь. Це звучить дуже офіційно та остаточно.
Тіло

7
@JeffGohlke: "Немає способу відповісти на питання, чому ви не є тим, хто прийняв рішення". ... ну коментар gparyani доводить інакше
user541686

Відповіді:


145

Як damryfbfnetsi балів з в коментарях, JLS §14.11 має наступну примітку:

Заборона використовувати nullяк мітку комутатора заважає писати код, який ніколи не може бути виконаний. Якщо switchвираз має еталонний тип, тобто Stringабо примітивний тип в коробці або тип перерахунку, то помилка під час виконання буде виникати, якщо вираз оцінюється nullна час виконання. На думку дизайнерів мови програмування Java, це кращий результат, ніж мовчки пропустити весь switchвислів або вибрати виконання операцій (якщо такі є) після defaultмітки (якщо такі є).

(наголос мій)

Поки останнє речення пропускає можливість використання case null: , воно здається розумним і пропонує погляд на наміри дизайнерів мови.

Якщо ми скоріше розглянемо деталі впровадження, у цій публікації блогу Крістіана Худжера є глибока міркування про те, чому nullце не дозволяється в комутаторах (хоча він зосереджений на enumкомутаторі, а не на Stringкомутаторі):

Під капотом switchоператор, як правило, компілюється в байт-код табличного переключення. І "фізичний" аргумент switch, а також його випадки - ints. Значення int для включення визначається шляхом виклику методу Enum.ordinal(). Порядки [...] починаються з нуля.

Це означає, що відображення , nullщоб 0не було б гарною ідеєю. Перемикання першого значення перерахування не відрізнятиметься від нуля. Можливо, було б гарною ідеєю почати рахувати порядки для перерахунків з 1. Однак це не було визначено так, і це визначення неможливо змінити.

Хоча Stringкомутатори реалізовані по-різному , enumперемикач прийшов першим і встановив прецедент того, як повинен поводитися тип переліку, коли посилання єnull .


1
Було б велике вдосконалення, щоб дозволити обробку нуля як частину, case null:якщо це було б застосовано виключно для String. В даний час вся Stringперевірка вимагає будь-якої нульової перевірки, якщо ми хочемо зробити це правильно, хоча в основному неявно, поставивши спочатку строкову константу як в "123test".equals(value). Тепер ми змушені писати нашу заяву про перемикання якif (value != null) switch (value) {...
YoYo

1
Повторно "відображення нуля в 0 не було б гарною ідеєю": це заниження, оскільки значення "" .hashcode () дорівнює 0! Це означало б, що нульову рядок і нульову довжину слід обробляти однаково в операторі комутатора, що явно не є життєздатним.
skomisa

Що стосується перерахунків, що стримувало їх відображення нуля до -1?
krispy

31

Взагалі nullпогано поводитися; можливо, краща мова може жити без null.

Можливо, вашу проблему вирішить

    switch(month==null?"":month)
    {
        ...
        //case "":
        default: 
            monthNumber = 0;

    }

Непогана ідея, якщо monthце порожня рядок: це трактуватиметься так само, як і нульовий рядок.
gparyani

13
Для багатьох випадків трактування нуля як порожнього рядка може бути цілком розумним
Ерік Вудруфф

23

Це не дуже, але String.valueOf()дозволяє використовувати null String в комутаторі. Якщо він знайде null, він перетворює його в "null"інше, інакше він просто повертає ту саму рядок, яку ви пройшли. Якщо ви не впораєтеся "null"явно, тоді це перейде до default. Єдине застереження полягає в тому, що немає способу розрізнити між String "null"і фактичною nullзмінною.

    String month = null;
    switch (String.valueOf(month)) {
        case "january":
            monthNumber = 1;
            break;
        case "february":
            monthNumber = 2;
            break;
        case "march":
            monthNumber = 3;
            break;
        case "null":
            monthNumber = -1;
            break;
        default: 
            monthNumber = 0;
            break;
    }
    return monthNumber;

2
Я вважаю, що робити щось подібне в Java - це антипатерн.
Łukasz Rzeszotarski

2
@ ŁukaszRzeszotarski Це майже все, що я мав на увазі під «це не дуже»
krispy

15

Це спроба відповісти, чому це кидає NullPointerException

Вихід команди javap нижче показує, що caseвибирається на основі хеш-коду switchаргументального рядка і, отже, викидає NPE, коли .hashCode()викликається на null string.

6: invokevirtual #18                 // Method java/lang/String.hashCode:()I
9: lookupswitch  { // 3
    -1826660246: 44
     -263893086: 56
      103666243: 68
        default: 95
   }

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

    int monthNumber;
    String month = args[0];

    switch (month) {
    case "Ea":
        monthNumber = 1;
        break;
    case "FB":
        monthNumber = 2;
        break;
    // case null:
    default:
        monthNumber = 0;
        break;
    }
    System.out.println(monthNumber);

javap для чого

  10: lookupswitch  { // 1
              2236: 28
           default: 59
      }
  28: aload_3       
  29: ldc           #22                 // String Ea
  31: invokevirtual #24                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  34: ifne          49
  37: aload_3       
  38: ldc           #28                 // String FB
  40: invokevirtual #24                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  43: ifne          54
  46: goto          59 //Default

Як ви можете бачити тільки один випадок отримує генеруються для "Ea"і , "FB"але з двома ifумовами для перевірки на збіг з кожним рядком випадку. Дуже цікавий і складний спосіб реалізації цього функціоналу!


5
Це могло бути реалізовано і іншим способом.
Тило

2
Я б сказав, що це помилка в дизайні.
Мисливець на оленів

6
Цікаво, чи це пов’язано з тим, чому деякі сумнівні аспекти функції хеш-рядків не були змінені: загалом код не повинен покладатися на hashCodeповернення одних і тих же значень у різних запусках програми, але оскільки хешові рядки запускаються у виконувані файли компілятор, метод хеш-рядків закінчується частиною мовної специфікації.
supercat

5

Довга коротка історія ... (і, сподіваємось, досить цікава !!!)

Enum були вперше представлені в Java1.5 ( вересень 2004 р. ), І помилка з проханням дозволити перемикання на String була подана довгий час ( жовтень 95 ). Якщо ви подивитесь на коментар, опублікований на цю помилку в червні 2004 року , він каже, що, Don't hold your breath. Nothing resembling this is in our plans.схоже, вони відклали ( проігнорували ) цю помилку і врешті-решт запустили Java 1.5 в тому ж році, коли вони ввели "enum" з порядковим початком на 0 і вирішили ( пропустив ) не підтримувати null для enum. Пізніше в Java1.7 ( липень 2011 р. ) Вони пішли ( вимушено) ) Та ж філософія з String (тобто під час генерації байтового коду перед викликом методу hashcode ( ) не проводилася перевірка нуля).

Тому я думаю, що це зводиться до того, що enum з'явився спочатку і був реалізований з його порядковим початком у 0, через що вони не могли підтримувати нульове значення в блоці комутаторів, а пізніше за допомогою String вони вирішили примусити ту саму філософію, тобто нульове значення не дозволено в блоці комутаторів.

TL; DR Завдяки String вони могли б подбати про NPE (викликаний спробою генерування хеш-коду для null), застосовуючи код Java для перетворення байтового коду, але нарешті вирішили не робити.

Посилання: TheBUG , JavaVersionHistory , JavaCodeToByteCode , SO


1

За даними Java Docs:

Перемикач працює з примітивними типами байтів, коротких, знаків та int. Він також працює з переліченими типами (обговорюється в Enum Types), класом String та декількома спеціальними класами, які містять певні примітивні типи: Character, Byte, Short та Integer (обговорюється в Numbers and Strings).

Оскільки nullвін не має типу і не є примірником нічого, він не працюватиме з оператором switch.


4
І тим НЕ менш nullє допустимим значенням для String, Character, Byte, Short, або Integerпосилання.
asteri

0

Відповідь просто полягає в тому, що якщо ви використовуєте комутатор із еталонним типом (наприклад, коробчатим примітивним типом), помилка під час виконання буде виникати, якщо вираз є нульовим, оскільки розпакування воно би кинуло NPE.

тож випадок null (який є незаконним) ніколи не міг би бути виконаний;)


1
Це могло бути реалізовано і іншим способом.
Тіло

Гаразд @Thilo, розумніші люди, ніж я, були залучені до цієї реалізації. Якщо ви знаєте інші способи, якими це могло б бути здійснено, я хотів би знати, що це [а я впевнений, що є й інші], тому поділіться ...
amrith

3
Рядки не є коробками примітивних типів, і NPE не виникає, оскільки хтось намагається їх "розблокувати".
Тило

@thilo, інші способи цього втілити, що?
Амріт

3
if (x == null) { // the case: null part }
Тіло

0

Я погоджуюся з проникливими коментарями (Під кришкою ....) у https://stackoverflow.com/a/18263594/1053496 у відповіді @Paul Bellora.

Я знайшов ще одну причину зі свого досвіду.

Якщо 'case' може бути null, це означає, що перемикач (змінної) є null, якщо розробник надає збіг 'null', тоді можна стверджувати, що це добре. Але що станеться, якщо розробник не надасть жодного "нульового" випадку. Тоді ми повинні зіставити його із випадком "за замовчуванням", який може бути не тим, що розробник мав намір обробляти у випадку за замовчуванням. Тому відповідність "null" до замовчування може спричинити "дивовижну поведінку". Тому кидання "NPE" змушує розробника чітко обробляти всі випадки. Мені здалося, що метання NPE в цьому випадку дуже продумане.


0

Використовуйте клас Apache StringUtils

String month = null;
switch (StringUtils.trimToEmpty(month)) {
    case "xyz":
        monthNumber=1;  
    break;
    default:
       monthNumber=0;
    break;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.