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


125

Мне просто интересно, почему switchоператор Java 7 не поддерживает nullрегистр, а вместо этого бросает NullPointerException? См. Прокомментированную строку ниже (пример взят из статьи Java Tutorialsswitch ):

{
    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во время выполнения] - лучший результат, чем молчаливый пропуск всего оператора switch или выбор выполнения операторов (если есть) после метка по умолчанию (если есть).
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оператор обычно компилируется в байтовый код tablesswitch. И «физический» аргумент, switchтак и его случаи - ints. Значение int для включения определяется путем вызова метода Enum.ordinal(). [...] ординалы начинаются с нуля.

Это означает, что сопоставление nullс ним - 0не лучшая идея. Переключение на первое значение перечисления было бы неотличимо от null. Возможно, было бы неплохо начать подсчет порядковых номеров для перечислений с 1. Однако это не было определено таким образом, и это определение не может быть изменено.

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


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

1
Повторное «сопоставление нуля с 0 - не лучшая идея»: это преуменьшение, поскольку значение «» .hashcode () равно 0! Это означало бы, что пустая строка и строка нулевой длины должны обрабатываться одинаково в операторе switch, что явно нежизнеспособно.
скомиса

В случае с enum, что мешало им сопоставить ноль с -1?
krispy

31

Вообще nullнеприятно обращаться; возможно, лучший язык может жить без null.

Ваша проблема может быть решена

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

    }

Не лучшая идея, если monthэто пустая строка: это будет обрабатывать ее так же, как пустую строку.
gparyani

13
Во многих случаях обработка null как пустой строки может быть вполне разумной
Эрик Вудрафф

23

Это некрасиво, но String.valueOf()позволяет использовать в переключателе пустую строку. Если он находит 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 - это антипаттерн.
Лукаш Жешотарски

2
@ UkaszRzeszotarski Это примерно то, что я имел в виду под "это
некрасиво

15

Это попытка ответить, почему бросает NullPointerException

Вывод команды javap ниже показывает, что caseона выбрана на основе хэш- кода switchстроки аргумента и, следовательно, выдает NPE при .hashCode()вызове с нулевой строкой.

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
Я бы сказал, что это ошибка дизайна.
Deer Hunter

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

5

Короче ... (и, надеюсь, достаточно интересно !!!)

Enum были впервые представлены в Java1.5 ( сентябрь 2004 г. ), и ошибка, запрашивающая разрешение на включение String, была давно подана ( октябрь 1995 г. ). Если вы посмотрите на комментарий, опубликованный к этой ошибке в июне 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 () не выполнялась проверка на null).

Итак, я думаю, что это сводится к тому факту, что enum пришло первым и было реализовано с его порядковым номером, начинающимся с 0, из-за чего они не могли поддерживать нулевое значение в блоке переключателя, а позже с помощью String они решили принудительно использовать ту же философию, то есть нулевое значение не разрешено в блоке переключателя.

TL; DR С помощью String они могли позаботиться о NPE (вызванном попыткой сгенерировать хэш-код для null) при реализации преобразования java-кода в преобразование байтового кода, но в конце концов решили не делать этого.

Ссылка: TheBUG , JavaVersionHistory , JavaCodeToByteCode , SO


1

Согласно Java Docs:

Переключатель работает с примитивными типами данных byte, short, char и int. Он также работает с перечисляемыми типами (обсуждаемыми в разделе «Типы перечислений»), классом String и несколькими специальными классами, которые охватывают определенные примитивные типы: символьный, байтовый, короткий и целочисленный (обсуждаются в разделе «Числа и строки»).

Поскольку nullне имеет типа и не является экземпляром чего-либо, он не будет работать с оператором switch.


4
И тем не менее nullявляется допустимым значением для String, Character, Byte, Short, или Integerссылки.
asteri

0

Ответ прост: если вы используете переключатель со ссылочным типом (например, примитивный тип в штучной упаковке), ошибка времени выполнения возникнет, если выражение имеет значение NULL, потому что при его распаковке возникает NPE.

так что case null (что является незаконным) никогда не может быть выполнен;)


1
Однако это можно было реализовать по-другому.
Тило

ОК @Thilo, в этой реализации были задействованы люди более умные, чем я. Если вы знаете другие способы, которыми это могло быть реализовано, я хотел бы знать, что это такое [и я уверен, что есть и другие], так что поделитесь ...
Амрит

3
Строки не являются примитивными типами в штучной упаковке, и NPE не возникает, потому что кто-то пытается их «распаковать».
Тило

@thilo, другие способы реализовать это, какие?
Амрит

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

0

Я согласен с проницательными комментариями (Под капотом ....) в https://stackoverflow.com/a/18263594/1053496 в ответе @Paul Bellora.

Я нашел еще одну причину из своего опыта.

Если case может иметь значение null, это означает, что switch (переменная) имеет значение 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.