Почему String.valueOf(null) вызывает исключение NullPointerException?

согласно документации, метод String.valueOf(Object obj) возвращает:

если аргумент null, тогда строка равна "null"; в противном случае возвращается значение obj.toString().

Но как получится, когда я попытаюсь сделать это:

System.out.println("String.valueOf(null) = " + String.valueOf(null));

он вместо этого заменяет NPE? (попробуйте сами, если не верите!)

    Exception in thread "main" java.lang.NullPointerException
    at java.lang.String.(Unknown Source)
    at java.lang.String.valueOf(Unknown Source)

Как это происходит? Является ли документация лживой для меня? Является ли это основной ошибкой в ​​Java?

Ответ 1

Проблема заключается в том, что метод String.valueOf перегружен:

Язык спецификации Java указывает, что в этих случаях выбирается наиболее специфическая перегрузка:

JLS 15.12.2.5 Выбор наиболее конкретного метода

Если более чем один метод-член доступен и применим к вызову метода, необходимо выбрать его для предоставления дескриптора для отправки времени выполнения. Язык программирования Java использует правило, в котором выбран наиболее специфический метод.

A char[] is-an Object, но не все Object is-a char[]. Поэтому char[] более специфичен, чем Object, и, как указано языком Java, в этом случае выбирается перегрузка String.valueOf(char[]).

String.valueOf(char[]) ожидает, что массив будет не null, а так как null задан в этом случае, он затем бросает NullPointerException.

Легкое "исправление" заключается в том, чтобы явно выразить null на Object следующим образом:

System.out.println(String.valueOf((Object) null));
// prints "null"

Связанные вопросы


Мораль истории

Есть несколько важных из них:

  • Эффективное Java 2nd Edition, пункт 41: разумно использовать перегрузку
    • Просто потому, что вы можете перегружать, не означает, что вы должны каждый раз
    • Они могут вызвать путаницу (особенно если методы имеют дико разные вещи)
  • Используя хорошую среду IDE, вы можете проверить, какая перегрузка выбрана во время компиляции
    • С Eclipse вы можете навести курсор мыши на вышеупомянутое выражение и увидеть, что действительно выбрана перегрузка valueOf(char[])!
  • Иногда вы хотите явно лить null (примеры следовать)

См. также


При отливке null

Существует, по крайней мере, две ситуации, когда необходимо явно указать null на конкретный ссылочный тип:

  • Чтобы выбрать перегрузку (как указано в приведенном выше примере)
  • Чтобы дать null как единственный аргумент параметра vararg

Простым примером последнего является следующее:

static void vararg(Object... os) {
    System.out.println(os.length);
}

Тогда мы можем иметь следующее:

vararg(null, null, null); // prints "3"
vararg(null, null);       // prints "2"
vararg(null);             // throws NullPointerException!

vararg((Object) null);    // prints "1"

См. также

Связанные вопросы

Ответ 2

Проблема в том, что вы вызываете String.valueOf(char[]) и не String.valueOf(Object).

Причиной этого является то, что Java всегда будет выбирать наиболее специфичную версию перегруженного метода, который работает с предоставленными параметрами. null является допустимым значением для параметра Object, но оно также является допустимым значением для параметра char[].

Чтобы сделать Java использующей версию Object, либо перейдите в null через переменную, либо укажите явное приведение в Object:

Object o = null;
System.out.println("String.valueOf(null) = " + String.valueOf(o));
// or
System.out.println("String.valueOf(null) = " + String.valueOf((Object) null));

Ответ 3

В 2003 году была зарегистрирована ошибка, пронумерованная 4867608, которая была решена как "не будет исправлена ​​" с этим объяснением.

Мы не можем изменить это из-за ограничений совместимости. Обратите внимание, что это общедоступный статический метод String valueOf (char data []), который заканчивается и в нем не упоминается замена "null" для null аргументы.

@###. ### 2003-05-23

Ответ 4

Если вы посмотрите на javadoc для методов String.valueOf(), вы увидите, что в нем фактически указано, передается ли значение null в метод, который будет выполняться через NullPointerException. Кроме того, если ваша IDE сделает это, вы можете посмотреть исходный код для этих методов, и вы увидите, что есть обычный код, который специфически нацелен на возможное, чтобы вход был пустым и выбрал NullPointerException.