Boolean.valueOf () czasami tworzy wyjątek NullPointerException


115

Mam ten kod:

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
    }
}

Mój problem polega na tym, że nie rozumiem, dlaczego Test 3 działa dobrze (drukuje falsei nie produkuje NullPointerException), tymczasem Test 4 wyrzuca plik NullPointerException. Jak widać w testach 1 i 2 , nulli modifiedItems.get("item1")są równe i null.

Zachowanie jest takie samo w Javie 7 i 8.


modifiedItems.get ("item1") to jest null, jesteś tego świadomy, ale zakładasz, że przekazanie tego do valueOf nie zakończy się w NPE?
Stultuske

16
@Stultuske: To poprawne pytanie, biorąc pod uwagę, że tylko dwa wiersze powyżej przekazania literału nulldo tej samej funkcji nie generują NPE! Jest ku temu dobry powód, ale na pierwszy rzut oka jest to z pewnością mylące :-)
psmears

25
Jestem pod wrażeniem. To jest najciekawsze pytanie o wyjątek zerowego wskaźnika, jakie widziałem od lat.
candied_orange

@Jeroen, to nie jest naśladownictwo tego pytania . Chociaż prawdą jest, że rozpakowywanie jest wspólne dla tych dwóch problemów, nie ma tu porównania. Kluczową kwestią w tym pytaniu jest to, że pojawia się ono ze względu na sposób rozwiązywania problemów z przeciążeniami; a to zupełnie inna rzecz niż sposób ==stosowania.
Andy Turner

Odpowiedzi:


178

Musisz uważnie przyjrzeć się, które przeciążenie jest wywoływane:

  • Boolean.valueOf(null)woła Boolean.valueOf(String). To nie zgłasza NPEnawet, jeśli jest dostarczony z parametrem null.
  • Boolean.valueOf(modifiedItems.get("item1"))wywołuje Boolean.valueOf(boolean), ponieważ modifiedItemswartości są typu Boolean, który wymaga konwersji po rozpakowaniu. Ponieważ modifiedItems.get("item1")jest null, to rozpakowanie tej wartości - a nie Boolean.valueOf(...)- powoduje odrzucenie NPE.

Zasady określania, które przeciążenie jest wywoływane, są dość zawiłe , ale z grubsza wyglądają tak:

  • W pierwszym przebiegu szukane jest dopasowanie metody bez zezwalania na pakowanie / rozpakowywanie (ani metody ze zmienną arancją).

    • Ponieważ nulljest to dopuszczalna wartość dla a, Stringale nie boolean, Boolean.valueOf(null)jest dopasowywana do Boolean.valueOf(String)w tym przebiegu;
    • Booleannie jest akceptowalne dla albo Boolean.valueOf(String)lub Boolean.valueOf(boolean), więc żadna metoda nie jest dopasowywana w tym przebiegu for Boolean.valueOf(modifiedItems.get("item1")).
  • W drugim przebiegu wyszukiwane jest dopasowanie metody, co pozwala na umieszczanie w pudełku / rozpakowywanie (ale nadal nie metody ze zmienną aranżacją).

    • A Booleanmożna rozpakować do boolean, więc Boolean.valueOf(boolean)jest dopasowywane Boolean.valueOf(modifiedItems.get("item1"))w tym przebiegu; ale kompilator musi wstawić konwersję po rozpakowaniu, aby ją wywołać:Boolean.valueOf(modifiedItems.get("item1").booleanValue())
  • (Istnieje trzeci przebieg zezwalający na metody ze zmienną aranżacją, ale nie ma to znaczenia w tym przypadku, ponieważ pierwsze dwa przebiegi pasowały do ​​tych przypadków)


3
Czy kod mógłby być bardziej przejrzysty, gdybyśmy użyli go Boolean.valueOf(modifiedItems.get("item1").booleanValue())w kodzie źródłowym zamiast Boolean.valueOf(modifiedItems.get("item1"))?
Spowodowanie niedomiarów wszędzie

1
@CausingUnderflows Wszędzie nie do końca - naprawdę ciężko jest to dostrzec .booleanValue()w wyrażeniu. Dwie spostrzeżenia: 1) automatyczne (od) boksowanie to celowa funkcja Javy służąca do usuwania składniowego cruft; robienie tego samemu jest możliwe, ale nie idiomatyczne; 2) to w ogóle Ci nie pomaga - z pewnością nie zapobiega występowaniu problemu ani nie daje żadnych dodatkowych informacji, gdy wystąpi awaria (ślad stosu byłby identyczny, ponieważ wykonywany kod jest identyczny).
Andy Turner

@CausingUnderflows Wszędzie tam, gdzie lepiej jest użyć narzędzi do podkreślenia problemów, np. Intellij zarobi tutaj potencjalne NPE.
Andy Turner

13

Ponieważ modifiedItems.getzwraca a Boolean(którego nie można rzutować na a String), użyty zostałby podpis Boolean.valueOf(boolean), w którym Booleanjest wysyłany do prymitywu boolean. Po nullpowrocie tam wysyłanie kończy się niepowodzeniem z rozszerzeniem NullPointerException.


11

Podpis metody

Metoda Boolean.valueOf(...)ma dwa podpisy:

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

Twoja modifiedItemswartość to Boolean. Nie możesz przesyłać Booleando, Stringwięc w konsekwencji zostanie wybrany pierwszy podpis

Boolean unboxing

W swoim oświadczeniu

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

które można odczytać jako

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

Jednak modifiedItems.get("item1")wraca, nullwięc w zasadzie będziesz mieć

null.booleanValue()

co oczywiście prowadzi do NullPointerException


Nieprawidłowe sformułowanie, dziękuję za wskazanie, a odpowiedź jest aktualizowana po Twojej opinii. Przepraszam, nie widziałem Twojej odpowiedzi podczas pisania i widzę, że moja wygląda jak Twoja. Czy powinienem usunąć odpowiedź, aby uniknąć nieporozumień w przypadku OP?
Al-un

4
Nie usuwaj go na moim koncie. Pamiętaj, że to nie jest gra o sumie zerowej: ludzie mogą (i robią) głosować za wieloma odpowiedziami.
Andy Turner

3

Jak Andy bardzo dobrze opisał powód NullPointerException:

co wynika z logicznego un-boxingu:

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

przekształcić się w:

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

w czasie wykonywania, a następnie zgłasza NullPointerExceptionif modifiedItems.get("item1")jest null.

Teraz chciałbym dodać jeszcze jeden punkt, że rozpakowywanie następujących klas do ich odpowiednich prymitywów może również powodować NullPointerExceptionwyjątek, jeśli odpowiadające im zwracane obiekty mają wartość null.

  1. bajt - bajt
  2. char - znak
  3. float - Float
  4. int - Integer
  5. długi - długi
  6. krótki - krótki
  7. podwójny podwójny

Oto kod:

    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
„Konwertowany na… w czasie wykonywania” jest konwertowany do tego w czasie kompilacji.
Andy Turner

0

Sposób na zrozumienie tego jest taki, że kiedy Boolean.valueOf(null)jest wywoływany, java jest precyzyjnie instruowana, aby oszacować wartość null.

Jednak po Boolean.valueOf(modifiedItems.get("item1"))wywołaniu java otrzymuje polecenie uzyskania wartości z HashTable typu obiektu Boolean, ale nie znajduje typu Boolean, zamiast tego znajduje ślepy zaułek (null), mimo że oczekiwał wartości Boolean. Wyjątek NullPointerException jest generowany, ponieważ twórcy tej części javy zdecydowali, że ta sytuacja jest przypadkiem nieprawidłowego działania programu, który wymaga uwagi programisty. (Stało się coś niezamierzonego.)

W tym przypadku bardziej chodzi o różnicę między celowym deklarowaniem, że zamierzasz znaleźć wartość null, a java znajdowaniem brakującego odniesienia do obiektu (null), w którym obiekt miał zostać znaleziony.

Zobacz więcej informacji o wyjątku NullPointerException w tej odpowiedzi: https://stackoverflow.com/a/25721181/4425643


Jeśli ktoś może pomóc poprawić tę odpowiedź, myślałem o słowie, które odnosi się do programisty piszącego coś z jasnym zamiarem, bez dwuznaczności
CausingUnderflowsEverywhere
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.