2014-04-10 6 views
25
public class Npe { 
    static class Thing { 
     long value; 
    } 

    public static Map<Thing, Long> map; 

    public static void main(String[] args) { 
     Thing thing = new Thing(); 
     method(null); // returns -1 
     method(thing); // returns 0 
     map = new HashMap<Thing, Long>(); 
     method(null); // returns -1 
     method(thing); // NullPointerException thrown inside this method call 
    } 

    public static long method(Thing thing) { 
     if (thing == null) { 
      return -1; 
     } 
     Long v = (map == null) ? thing.value : map.get(thing); // NPE here 
     if (v == null) { 
      v = thing.value; 
     } 
     return v; 
    } 
} 

Na 4. wezwanie do method() dostaję NullPointerException rzucony na wskazanej linii wewnątrz method(). Jeśli byłaby to wiersz zDlaczego to przydział powoduje NPE?

Long v = (map == null) ? thing.value : map.get(thing); 

do

Long v; 
if (map == null) { 
    v = thing.value; 
} else { 
    v = map.get(thing); 
} 

mam żadnego NullPointerException i sposób zachowuje się tak jak powinno. Pytanie brzmi: DLACZEGO ??

Wygląda mi na to, kompilator spodziewa wynikiem operatora ? być long tak, że jest on automatycznie unboxing (obniżania od Long do long) wynik wywołania map.get(thing) (która może powrócić null a zatem rzucać NullPointerException). IMHO powinien oczekiwać, że wynik operatora będzie oznaczony jako Long i będzie to oznaczać jako autoboxing (promowanie long do Long) thing.value.

Jeszcze lepiej, jeśli byłaby to stwierdzenie:

Long v = (map == null) ? thing.value : map.get(thing); 

do tego (odlewanie long do Long jawnie):

Long v = (map == null) ? (Long)thing.value : map.get(thing); 

moja IDE (IntelliJ) mówi, że obsada jest zbędny, ale skompilowany kod działa zgodnie z oczekiwaniami i nie rzuca NullPointerException! :-D

+2

możliwy duplikat [Booleans, operatorów warunkowych i autoboxing] (http://stackoverflow.com/questions/3882095/booleans-conditional-operators-and-autoboxing) –

+10

@DwB Twój komentarz nie jest istotny, ponieważ nie jestem młodszy programista. Twoja sugestia, aby "nie używać operatora trójskładnikowego" również jest głupia. To, że coś może być skomplikowane lub mylące, nie oznacza, że ​​nie powinieneś go używać. Oznacza to tylko, że musisz być ostrożny i wiedzieć, co robisz. Gdyby programiści nigdy nie używali niczego, co było skomplikowane lub potencjalnie mylące, wszyscy byliby malarzami lub zamiataczami ulic lub ogrodnikami zamiast programistami ;-) –

+1

Jeśli nie rozumiesz operatora trójskładnikowego, to, uwierz lub nie, jesteś jeszcze młodszy z Jawa. Również fakt, że coś jest skomplikowane, mylące i nie ma żadnej wartości, jest doskonałym powodem, aby go nie używać. Gdyby deweloperzy nigdy nie używali niczego, co było skomplikowane i potencjalnie mylące, produkowaliby kod, który byłby mniej niepotrzebnie skomplikowany i dezorientujący. – DwB

Odpowiedz

28

Zastanów się wyrażenie warunkowe:

(map == null) ? thing.value : map.get(thing) 

Wynikiem tego wyrażenia będzie long, ponieważ typ thing.value jest long. Zobacz JLS §15.25 - Conditional Operator. Stół w JLS 8 to świetny dodatek. Wyjaśnia wszystkie możliwe typy wyjść dla różnych typów wejść. Tyle było zamieszania związanego z typem wyrażeń warunkowych.

Teraz po wywołaniu tej metody, jak:

method(thing); 

map nie jest null, więc warunek map == null w swojej wypowiedzi ocenia się false, a następnie ocenia map.get(thing) aby uzyskać wynik.

Ponieważ nie ma jeszcze wpisu w polu map, map.get(thing) zwróci null. Ale ponieważ typ wyniku to long, operacja rozpakowywania jest wykonywana na null, co skutkuje NPE.


Teraz, kiedy wyraźnie cast thing.value do Long, rodzaj ekspresji staje Long. Dlatego nie wykonuje się rozpakowywania z wynikiem map.get(thing), a null jest przypisany do Long v.

+4

* Często * Java gotcha :) –

+0

Dlaczego mówisz, że wynik wyrażenia będzie "długi"? Powiedziałbym, że wynikiem wyrażenia jest 'Long', ponieważ trzecim argumentem jest' Long'. –

+1

@DavidWasser Wynik wyrażenia warunkowego zależy od typu drugiego i trzeciego argumentu. Nie tylko na operand, który będzie odpowiedzią. Zobacz tabelę w łączu JLS. Jeśli którykolwiek z operandów jest typu pierwotnego, wynik będzie typem pierwotnym. –

0

To jest moje zrozumienie tego, co się dzieje:

podczas korzystania Long v = (map == null) ? thing.value : map.get(thing); // NPE here

map.get(thing) zwraca Long czyli null i wtedy nie jest próbą unbox jej wartość long (bo typu ekspresyjnego jeśli długo) - powoduje to NPE.

Jednak podczas korzystania z długiego formularza starannie unika się operacji rozpakowywania o wartości NULL Long.

+0

ok Używam wersji IE, która nie wierzy w pokazywanie aktualizacji SO tak szybko, jak to się dzieje, a nie w ciągu 22 minut co najmniej :) – Bhaskar