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

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

{
    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.

Ответ 1

Поскольку damryfbfnetsi указывает на в комментариях, JLS §14.11 имеет следующее Примечание:

Запрет на использование null в качестве метки коммутатора не позволяет писать код, который никогда не может быть выполнен. Если выражение switch относится к ссылочному типу, то есть String или вложенному типу примитива или типу перечисления, тогда будет выполняться ошибка времени выполнения, если выражение оценивается как null во время выполнения. По мнению разработчиков языка программирования Java, это лучший результат, чем молча пропустить весь оператор switch или выбрать выполнение операторов (если они есть) после метки default (если есть).

(акцент мой)

В то время как последнее предложение пропускает возможность использования case null:, оно кажется разумным и предлагает представление о намерениях разработчиков языка.

Если мы скорее посмотрим на детали реализации, в этом сообщении в блоге от Christian Hujer есть некоторые проницательные предположения о том, почему null не разрешено в (хотя он находится на переключателе enum, а не на переключателе String):

Под капотом оператор switch обычно компилируется с помощью байтового кода tablesswitch. И "физический" аргумент switch, а также его случаи - int s. Значение int для включения определяется путем вызова метода Enum.ordinal(). [...] ординалы начинаются с нуля.

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

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

Ответ 2

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

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

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

    }

Ответ 3

Это не очень, но String.valueOf() позволяет использовать пустую строку в коммутаторе. Если он находит null, он преобразует его в "null", иначе он просто возвращает ту же строну, которую вы ее передали. Если вы явно не обрабатываете "null", то он перейдет к default. Единственное предостережение состоит в том, что нет возможности различать строку "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;

Ответ 4

Это попытка ответить, почему он выбрасывает 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

Рассказ длинный... (и, надеюсь, достаточно интересный!!!)

Enum были впервые представлены в Java1.5 (Sep'2004) и bug запрос на разрешить включение строки был отложен назад ( октябрь). Если вы посмотрите комментарий, отправленный на эту ошибку в Jun'2004, он говорит: Don't hold your breath. Nothing resembling this is in our plans. Похоже, что они отложили (проигнорировали) эту ошибку и, в конечном итоге, запустили Java 1.5 в том же году, enum 'с порядковым номером начиная с 0 и решил (пропустил) не поддерживать нуль для перечисления. Позже в Java1.7 ( Jul'2011) они следовали (принудительно) с той же философией со String (т.е. while генерируя байт-код, не выполнялась нулевая проверка перед вызовом метода hashcode()).

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

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

Ref: TheBUG, JavaVersionHistory, JavaCodeToByteCode, fooobar.com/questions/11278/...

Ответ 6

В соответствии с Java Docs:

Коммутатор работает с байтовыми, короткими, char и int примитивными данными типы. Он также работает с перечисляемыми типами (обсуждаются в типах Enum), класс String и несколько специальных классов, которые обертывают определенные примитивные типы: символ, байт, короткий и целочисленный (обсуждаются в Номера и строки).

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

Ответ 7

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

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

Ответ 8

Я согласен с проницательными комментариями (под капотом....) в fooobar.com/questions/61041/... в ответе @Paul Bellora.

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

Если "case" может быть нулевым, что означает, что switch (variable) имеет значение null, то до тех пор, пока разработчик предоставляет соответствующий "null" случай, мы можем аргументировать его в порядке. Но что произойдет, если разработчик не предоставит никакого подходящего "нулевого" случая. Затем мы должны сопоставить его с дефолтным случаем, который может быть не тем, что разработчик должен обрабатывать в случае по умолчанию. Поэтому совпадение "null" с дефолтом может вызвать "удивительное поведение". Поэтому бросание "NPE" заставит разработчика обрабатывать все случаи явно. Я обнаружил, что NPE в этом случае очень задумчиво.

Ответ 9

Использовать класс Apache StringUtils

String month = null;
switch (StringUtils.trimToEmpty(month)) {
    case "xyz":
        monthNumber=1;  
    break;
    default:
       monthNumber=0;
    break;
}