Boolean.valueOf () иногда создает исключение NullPointerException

115

У меня есть такой код:

package tests;

import java.util.Hashtable;

public class Tests {

    public static void main(String[] args) {

        Hashtable<String, Boolean> modifiedItems = new Hashtable<String, Boolean>();

        System.out.println("TEST 1");
        System.out.println(modifiedItems.get("item1")); // Prints null
        System.out.println("TEST 2");
        System.out.println(modifiedItems.get("item1") == null); // Prints true
        System.out.println("TEST 3");
        System.out.println(Boolean.valueOf(null)); // Prints false
        System.out.println("TEST 4");
        System.out.println(Boolean.valueOf(modifiedItems.get("item1"))); // Produces NullPointerException
        System.out.println("FINISHED!"); // Never executed
    }
}

Моя проблема в том, что я не понимаю, почему Test 3 работает нормально (печатает falseи не производит NullPointerException), в то время как Test 4 выдает ошибку NullPointerException. Как видно из тестов 1 и 2 , nullи modifiedItems.get("item1")равны и null.

То же самое в Java 7 и 8.

Дэвид Э
источник
ModifiedItems.get ("item1") это null, вы знаете об этом, но предполагаете, что передача this в valueOf не завершится в NPE?
Stultuske
16
@Stultuske: Это правильный вопрос, учитывая, что всего двумя строками выше, передающими литерал одной nullи той же функции , не генерируется NPE! Для этого есть веская причина, но это определенно сбивает с толку на первый взгляд :-)
psmears
25
Я впечатлен. Это самый интересный вопрос об исключении нулевого указателя, который я видел за последние годы.
Candied_orange 06
@Jeroen, это не вопрос . Несмотря на то, что распаковка является общей для этих двух проблем, здесь сравнение не проводится. Ключевым моментом в этом вопросе является то, что он возникает из-за способа устранения перегрузок; и это совсем не то, как ==применяется.
Энди Тернер

Ответы:

178

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

  • Boolean.valueOf(null)вызывает Boolean.valueOf(String). Это не вызывает, NPEдаже если передано с нулевым параметром.
  • Boolean.valueOf(modifiedItems.get("item1"))вызывает Boolean.valueOf(boolean), потому что modifiedItemsзначения имеют тип Boolean, который требует преобразования распаковки. Так modifiedItems.get("item1")как nullэто распаковка этого значения, а не самого, Boolean.valueOf(...)вызывает NPE.

Правила определения того, какая перегрузка вызывается, довольно сложны , но примерно так:

  • На первом проходе ищется соответствие метода без разрешения упаковки / распаковки (а также методов переменной арности).

    • Поскольку nullявляется допустимым значением для , Stringно не boolean, Boolean.valueOf(null)согласован Boolean.valueOf(String)в этом проходе;
    • Booleanне является приемлемым для любой Boolean.valueOf(String)или Boolean.valueOf(boolean), так что ни один метод не согласован в этом проходе для Boolean.valueOf(modifiedItems.get("item1")).
  • Во втором проходе ищется соответствие метода, что позволяет упаковывать / распаковывать (но все же не методы переменной арности).

    • A Booleanможет быть распакован boolean, поэтому Boolean.valueOf(boolean)соответствует Boolean.valueOf(modifiedItems.get("item1"))в этом проходе; но преобразование распаковки должно быть вставлено компилятором, чтобы вызвать его:Boolean.valueOf(modifiedItems.get("item1").booleanValue())
  • (Есть третий проход, позволяющий использовать методы переменной арности, но здесь это не актуально, поскольку первые два прохода соответствовали этим случаям)

Энди Тернер
источник
3
Может ли код быть более понятным, если мы будем использовать его Boolean.valueOf(modifiedItems.get("item1").booleanValue())в исходном коде вместо Boolean.valueOf(modifiedItems.get("item1"))?
CausingUnderflowsEverywhere
1
@CausingUnderflowsEverywhere не совсем - действительно трудно увидеть, что скрыто .booleanValue()в выражении. Два наблюдения: 1) автоматическое (снятие) боксов - это преднамеренная функция Java для устранения синтаксического мусора; сделать это самому можно, но не идиоматично; 2) это вам совсем не помогает - это, конечно, не останавливает возникновение проблемы и не дает дополнительной информации, когда происходит сбой (трассировка стека будет идентична, потому что выполняемый код идентичен).
Энди Тернер
@CausingUnderflowsEverywhere лучше использовать инструменты, чтобы выделить проблемы, например, intellij расскажет вам о потенциальном NPE здесь.
Энди Тернер
13

Поскольку modifiedItems.getвозвращает Boolean(что не колдовать к String), подпись , которая будет использоваться в Boolean.valueOf(boolean), где Booleanнаходится перебоксировал к примитивным boolean. После nullвозврата туда исходящие сообщения завершаются с ошибкой NullPointerException.

Mureinik
источник
11

Подпись метода

У метода Boolean.valueOf(...)две сигнатуры:

  1. public static Boolean valueOf(boolean b)
  2. public static Boolean valueOf(String s)

Ваша modifiedItemsценность Boolean. Вы не можете Booleanвыполнить кастинг, Stringпоэтому будет выбрана первая подпись

Булево распаковка

В вашем заявлении

Boolean.valueOf(modifiedItems.get("item1"))

что можно прочитать как

Boolean.valueOf(modifiedItems.get("item1").booleanValue())   

Однако modifiedItems.get("item1")возвращается, nullтак что в основном у вас будет

null.booleanValue()

что, очевидно, приводит к NullPointerException

Аль-ип
источник
Неправильная формулировка, спасибо за указание, ответ обновляется после вашего отзыва. Извините, я не видел вашего ответа, пока писал, и вижу, что мой похож на ваш. Следует ли мне удалить свой ответ, чтобы избежать путаницы с OP?
Аль-ун
4
Не удаляйте его в моей учетной записи. Помните, что это не игра с нулевой суммой: люди могут (и делают) проголосовать за несколько ответов.
Энди Тернер
3

Как уже очень хорошо описал Энди причину NullPointerException:

что связано с булевой распаковкой:

Boolean.valueOf(modifiedItems.get("item1"))

преобразоваться в:

Boolean.valueOf(modifiedItems.get("item1").booleanValue())

во время выполнения, а затем бросает, NullPointerExceptionесли modifiedItems.get("item1")имеет значение null.

Теперь я хотел бы добавить здесь еще один момент, что распаковка следующих классов в их соответствующие примитивы также может вызвать NullPointerExceptionисключение, если их соответствующие возвращаемые объекты равны нулю.

  1. byte - Байт
  2. char - Персонаж
  3. float - Поплавок
  4. int - Целое число
  5. долго долго
  6. короткие - короткие
  7. двойной - Двойной

Вот код:

    Hashtable<String, Boolean> modifiedItems1 = new Hashtable<String, Boolean>();
    System.out.println(Boolean.valueOf(modifiedItems1.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Byte> modifiedItems2 = new Hashtable<String, Byte>();
    System.out.println(Byte.valueOf(modifiedItems2.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Character> modifiedItems3 = new Hashtable<String, Character>();
    System.out.println(Character.valueOf(modifiedItems3.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Float> modifiedItems4 = new Hashtable<String, Float>();
    System.out.println(Float.valueOf(modifiedItems4.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Integer> modifiedItems5 = new Hashtable<String, Integer>();
    System.out.println(Integer.valueOf(modifiedItems5.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Long> modifiedItems6 = new Hashtable<String, Long>();
    System.out.println(Long.valueOf(modifiedItems6.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Short> modifiedItems7 = new Hashtable<String, Short>();
    System.out.println(Short.valueOf(modifiedItems7.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Double> modifiedItems8 = new Hashtable<String, Double>();
    System.out.println(Double.valueOf(modifiedItems8.get("item1")));//Exception in thread "main" java.lang.NullPointerException
Мохит Тьяги
источник
1
«Преобразовано в ... во время выполнения» он преобразуется в это во время компиляции.
Энди Тернер
0

Способ понять, что это когда Boolean.valueOf(null)вызывается, java точно сообщает, что нужно оценить значение null.

Однако при Boolean.valueOf(modifiedItems.get("item1"))вызове java предлагается получить значение из HashTable типа объекта Boolean, но он не находит тип Boolean, а вместо этого находит тупик (null), хотя ожидал Boolean. Исключение NullPointerException выбрасывается, потому что создатели этой части java решили, что эта ситуация является примером того, что в программе что-то идет не так, что требует внимания программиста. (Произошло что-то непреднамеренное.)

В этом случае это больше разница между преднамеренным заявлением о том, что вы намеревались иметь нулевое значение, и обнаружением java отсутствующей ссылки на объект (null), где объект должен был быть найден.

Дополнительную информацию о NullPointerException см. В этом ответе: https://stackoverflow.com/a/25721181/4425643

CausingUnderflowsEverywhere
источник
Если кто-то может помочь улучшить этот ответ, я подумал о слове, которое относится к программисту, который пишет что-то с ясным намерением, без двусмысленности
CausingUnderflowsEverywhere