2013-07-09 21 views
12

Kiedy piszę mojego kodu Java tak:Dziwne zachowanie przy użyciu Java potrójny operator

Map<String, Long> map = new HashMap<>() 
Long number =null; 
if(map == null) 
    number = (long) 0; 
else 
    number = map.get("non-existent key"); 

aplikacja działa zgodnie z oczekiwaniami, ale kiedy to zrobić:

Map<String, Long> map = new HashMap<>(); 
Long number= (map == null) ? (long)0 : map.get("non-existent key"); 

otrzymuję NullPointerException na druga linia. Wskaźnik debugowania przeskakuje od drugiej linii do tej metody w klasie java.lang.Thread:

/** 
    * Dispatch an uncaught exception to the handler. This method is 
    * intended to be called only by the JVM. 
    */ 
    private void dispatchUncaughtException(Throwable e) { 
     getUncaughtExceptionHandler().uncaughtException(this, e); 
    } 

Co się tutaj dzieje? Obie te ścieżki kodowe są dokładnie równoważne, czyż nie?


Edit

używam Java 1.7 U25

+0

Dlaczego testujesz na niemożliwe? Sam tworzysz 'mapę'; jeśli jest 'null', twój system runtime jest uszkodzony. Ponieważ jest to oczywiście skrócona wersja innego wykonywanego przez ciebie dzieła, sugerowałbym, abyś upewniał się, że obiekty są zawsze inicjowane, więc nie musisz wykonywać sprawdzeń 'map == null'. Klucze to twój problem. –

+0

@Eric Jablow To jest tylko uproszczona wersja mojego kodu. Ta mapa jest faktycznie pobierana z innego miejsca, w którym są one dynamicznie tworzone na żądanie. Właśnie dlatego czek. Tutaj zawarłem inicjalizację, aby było jasne dla ludzi, którzy odpowiadają, że przypadek "prawda-prawdziwy" nie jest wykonywany, gdy pojawia się mój problem. – radiantRazor

+0

Teraz rozumiem. Prawdopodobnie najlepszą rzeczą byłoby natychmiastowe zgłoszenie wyjątku, gdyby ktoś przekazał nam kod 'null'. Lub, jeśli ktoś poda 'null' do twojego kodu, zamiast tego ustaw twoją mapę na pustą. –

Odpowiedz

15

Nie są one równoważne.

typ tego wyrażenia

(map == null) ? (long)0 : map.get("non-existent key"); 

jest long ponieważ prawdziwy wynik ma typ long.

Powodem tego wyrażenia jest typu long jest z sekcji §15.25 of the JLS:

Jeżeli jeden z drugim i trzecim argumentów jest pierwotny typu T i rodzaju innych jest wynikiem zastosowania konwersji boks (§5.1.7) do T, wówczas typem wyrażenia warunkowego jest T.

Kiedy Lookup nieistniejąca klucz na map powraca null. Tak, Java próbuje rozpakować go do long. Ale to jest null. Tak więc nie można i dostajesz NullPointerException. Możesz to naprawić, mówiąc:

Long number = (map == null) ? (Long)0L : map.get("non-existent key"); 

i wtedy będziesz w porządku.

Jednak tutaj,

if(map == null) 
    number = (long) 0; 
else 
    number = map.get("non-existent key"); 

od number jest zadeklarowana jako Long, że unboxing Do long nigdy nie występuje.

+0

W takim przypadku, czy plakat może używać operatora potrójnego przez rzutowanie 0 na 'Long' zamiast' long'? – MathSquared

+1

Tak, musiałby jednak rzucić '0L' na' Long'. – jason

+2

Woww, JLS ma tak wiele ukrytych połowów. Moje oczywiste pytanie uzupełniające brzmi: dlaczego JLS ma zdefiniować typ jako prymitywny? Jeśli zdefiniowano, że typ powinien zostać wywnioskowany jako typ autoboxed, wówczas można by uniknąć takich wyjątków nullpointer. Spróbuję zadać to pytanie jako osobne pytanie – radiantRazor

3

Co się tu dzieje? Obie te ścieżki kodowe są dokładnie równoważne, czyż nie?

Nie są one równoważne; operator trójskładnikowy ma kilka zastrzeżeń.

IF-prawda argument trójskładnikowego operatora (long) 0 jest z pierwotnego typu long. W konsekwencji, jeżeli fałsz argument automatycznie rozpakowanych z Long do long (zgodnie JLS §15.25)

Jeżeli jeden z drugim i trzecim argumentów jest pierwotny typu T i typu drugiego jest wynikiem zastosowania konwersji boksu (§5.1.7) na T, wówczas typem wyrażenia warunkowego jest T.

Jednak ten argument jest null (ponieważ Twoja mapa nie zawiera ciąg "non-existent key", co oznacza get() powraca null), więc NullPointerException występuje podczas procesu unboxing.

+0

Ale dlaczego typ wartości typu prawda-prawda wpływa na typ wartości if-false? Czy nie powinno to być określone przez typ na LHS? – radiantRazor

+1

@radiantRazor W skrócie, ponieważ w ten sposób JLS definiuje operatora trójskładnikowego. – arshajii

0

Skomentowałem powyżej, sugerując, że zapewnia on, że map nigdy nie będzie null, ale to nie pomoże w trójskładnikowym problemie. Z praktycznego punktu widzenia łatwiej jest systemowi wykonać pracę za Ciebie. Mógłby użyć Apache Commons Collections 4 i jego klasy DefaultedMap.

import static org.apache.commons.collections4.map.DefaultedMap.defaultedMap; 

Map<String, Long> map = ...; // Ensure not null. 
Map<String, Long> dMap = defaultedMap(map, 0L); 

Google Guava nie ma niczego takie proste, ale można owinąć map metodą Maps.transformValues().