Spójrzmy na prosty kod Java w następującym fragmencie:
public class Main {
private int temp() {
return true ? null : 0;
// No compiler error - the compiler allows a return value of null
// in a method signature that returns an int.
}
private int same() {
if (true) {
return null;
// The same is not possible with if,
// and causes a compile-time error - incompatible types.
} else {
return 0;
}
}
public static void main(String[] args) {
Main m = new Main();
System.out.println(m.temp());
System.out.println(m.same());
}
}
W tym najprostszym kodzie Java temp()
metoda nie powoduje błędu kompilatora, mimo że zwracany jest typ funkcji int
, i próbujemy zwrócić wartość null
(poprzez instrukcję return true ? null : 0;
). Po skompilowaniu powoduje to oczywiście wyjątek czasu wykonywania NullPointerException
.
Wydaje się jednak, że to samo jest nie tak, jeśli reprezentujemy operatora trójskładnikowego za pomocą if
instrukcji (jak w same()
metodzie), która powoduje błąd podczas kompilacji! Czemu?
int foo = (true ? null : 0)
inew Integer(null)
zarówno kompilacji porządku, druga jest wyraźne forma autoboxing.null
doInteger
... To mogłoby wyglądać jak „zgadywanie” lub „sprawianie, by działało” ...Integer foo() { return "1"; }
nie będzie się kompilować.)Odpowiedzi:
Kompilator interpretuje
null
jako odwołanie zerowe doInteger
, stosuje reguły autoboxowania / rozpakowywania dla operatora warunkowego (zgodnie z opisem w specyfikacji języka Java, 15.25 ) i szczęśliwie się porusza. Spowoduje to wygenerowanieNullPointerException
w czasie wykonywania, które można potwierdzić, wypróbowując.źródło
capture conversion
ilub(T1,T2)
)? Ponadto, czy naprawdę można zastosować boks do wartości zerowej? Czy to nie byłoby jak „zgadywanie”?lub(T1,T2)
jest najbardziej specyficznym typem odniesienia wspólnym w hierarchii typów T1 i T2. (Oba mają co najmniej obiekt, więc zawsze istnieje najbardziej specyficzny typ odniesienia.)null
nie jest zapakowane w liczbę całkowitą, jest interpretowane jako odwołanie do liczby całkowitej (odwołanie zerowe, ale to nie jest problem). Żaden obiekt liczb całkowitych nie jest konstruowany z wartości zerowej, więc nie ma powodu dla wyjątku NumberFormatException.null
(co nie jest prymitywnym typem liczbowym), odpowiednią klauzulą jest: „Jeśli p jest wartością dowolnego innego typu, konwersja boksu jest równoważna konwersji tożsamości „. Bokserska konwersjanull
naInteger
plonynull
, bez wywoływania jakiegokolwiekInteger
konstruktora.Myślę, że kompilator Java interpretuje
true ? null : 0
jakoInteger
wyrażenie, które można niejawnie przekonwertować naint
, być może podaćNullPointerException
.W drugim przypadku wyrażenie
null
ma specjalny typ zerowy patrz , więc kodreturn null
powoduje niedopasowanie typu.źródło
true ? null : 0
jakoInteger
?0
Najpierw autoboxing ??W rzeczywistości wszystko to wyjaśniono w specyfikacji języka Java .
Dlatego „null” w twoim
(true ? null : 0)
pobiera typ int, a następnie jest automatycznie przenoszony na Integer.Spróbuj czegoś takiego, aby to zweryfikować,
(true ? null : null)
a pojawi się błąd kompilatora.źródło
int
wartości z funkcji, która powoduje NPE.null
doInteger
znew Integer(null);
„Let T1 być typem, który wynika ze stosowania konwersji boks S1 ...” byś dostaćNumberFormatException
i to nie jest przypadek ...W przypadku
if
instrukcjinull
referencja nie jest traktowana jakoInteger
referencja, ponieważ nie uczestniczy w wyrażeniu, które zmusza ją do takiej interpretacji. Dlatego błąd można łatwo wychwycić w czasie kompilacji, ponieważ jest to wyraźniejszy błąd typu .Jeśli chodzi o operator warunkowy, specyfikacja języka Java § 15.25 „Operator warunkowy
? :
” odpowiada na to ładnie w zasadach stosowania konwersji typu:źródło
0
zostanie automatycznie przeniesione do,Integer
kompilator wykonuje ostatni przypadek „reguł operatora trójskładnikowego”, jak opisano w specyfikacji języka Java. Jeśli to prawda, trudno mi uwierzyć, że przeskoczyłbym wtedy do przypadku 3 tych samych reguł, które mają wartość zerową i typ odwołania, które sprawiają, że zwracana wartość operatora trójskładnikowego jest typem odniesienia (liczba całkowita). .Integer
? Właśnie tak się dzieje; NPE jest generowany przez próbę rozpakowania wartości wyrażenia w celu zwrócenia wartościint
z funkcji. Zmień funkcję na return an,Integer
a ona wrócinull
bez problemu.null
należy do tej kategorii . Następnie przejdziemy do kroku „W przeciwnym razie stosowana jest promocja binarna numeryczna (§5.6.2) ... Zauważ, że promocja binarna numeryczna wykonuje konwersję rozpakowywania (§5.1.8) ...” w celu ustalenia typu zwrotu. Ale konwersja rozpakowywania wygenerowałaby NPE, a dzieje się tak tylko w czasie wykonywania, a nie podczas próby określenia typu operatora trójskładnikowego. Nadal jestem zdezorientowany ..null
Jest traktowany tak, jakby miał typint
, ale jest rzeczywiście równoważnethrow new NullPointerException()
, to wszystko.Pierwszą rzeczą, o której należy pamiętać, jest to, że trójskładnikowe operatory Java mają „typ”, i to właśnie kompilator określi i rozważy bez względu na rzeczywiste / rzeczywiste typy drugiego lub trzeciego parametru. W zależności od kilku czynników typ operatora trójskładnikowego określa się na różne sposoby, jak pokazano w specyfikacji języka Java 15.26
W powyższym pytaniu powinniśmy rozważyć ostatni przypadek:
Jest to zdecydowanie najbardziej złożony przypadek, gdy przyjrzysz się zastosowaniu konwersji przechwytywania (§ 5.1.1), a przede wszystkim w lub (T1, T2) .
Prostym językiem angielskim i po skrajnym uproszczeniu możemy opisać ten proces jako obliczenie „najmniejszej wspólnej superklasy” (tak, pomyśl o LCM) drugiego i trzeciego parametru. To da nam trójskładnikowy „typ”. Ponownie, to, co właśnie powiedziałem, jest ekstremalnym uproszczeniem (rozważ klasy, które implementują wiele popularnych interfejsów).
Na przykład, jeśli spróbujesz:
Zauważysz, że wynikowym typem wyrażenia warunkowego jest
java.util.Date
to, że jest to „Najmniejsza wspólna nadklasa” dla paryTimestamp
/Time
pair.Ponieważ
null
może być autoboxowane do czegokolwiek, „Least Common Superclass” jestInteger
klasą i będzie to zwracany typ wyrażenia warunkowego (operator trójskładnikowy) powyżej. Zwracana wartość będzie wtedy zerowym wskaźnikiem typuInteger
i to, co zostanie zwrócone przez operator trójskładnikowy.W czasie wykonywania, gdy wirtualna maszyna Java się rozpakowuje, wyrzucane jest
Integer
aNullPointerException
. Dzieje się tak, ponieważ JVM próbuje wywołać funkcjęnull.intValue()
, gdzienull
jest wynikiem autoboxowania.Moim zdaniem (a ponieważ mojej opinii nie ma w specyfikacji języka Java, wiele osób i tak uzna to za błędne), kompilator źle radzi sobie z oceną wyrażenia w pytaniu. Biorąc pod uwagę, że napisałeś,
true ? param1 : param2
kompilator powinien od razu ustalić, że pierwszy parametr -null
- zostanie zwrócony i powinien wygenerować błąd kompilatora. Jest to nieco podobne do tego, kiedy piszesz,while(true){} etc...
a kompilator narzeka na kod pod pętlą i oznacza go flagąUnreachable Statements
.Druga sprawa jest dość prosta i ta odpowiedź jest już za długa ...;)
KOREKTA:
Po kolejnej analizie uważam, że nie miałem racji, twierdząc, że
null
wartość może być spakowana / autoboxed do dowolnego elementu. Mówiąc o klasie Integer, jawne boksowanie polega na wywołaniunew Integer(...)
konstruktora, a możeInteger.valueOf(int i);
(znalazłem tę wersję gdzieś). Pierwsze rzuciłobyNumberFormatException
(i tak się nie dzieje), a drugie nie miałoby sensu, ponieważint
nie możnanull
...źródło
null
W oryginalnym kodzie OP nie jest zapakowane. Działa to tak: kompilator zakłada, żenull
jest to odwołanie do liczby całkowitej. Korzystając z reguł dla typów wyrażeń trójskładnikowych, decyduje, że całe wyrażenie jest wyrażeniem całkowitym. Następnie generuje kod do autokoxowania1
(w przypadku, gdy warunek zostanie ocenionyfalse
). Podczas wykonywania warunek jest ocenianytrue
tak, aby wyrażenie wyrażało się jakonull
. Podczas próby zwrócenia anint
z funkcjinull
rozpakowuje się. To następnie rzuca NPE. (Kompilator może zoptymalizować większość tego.)W rzeczywistości w pierwszym przypadku wyrażenie może zostać ocenione, ponieważ kompilator wie, że należy je ocenić jako
Integer
, jednak w drugim przypadkunull
nie można określić typu wartości zwracanej ( ), więc nie można go skompilować. Jeśli go rzuciszInteger
, kod się skompiluje.źródło
źródło
Co powiesz na to:
Dane wyjściowe są prawdziwe, prawdziwe.
Kolor Eclipse koduje 1 w wyrażeniu warunkowym jako autoboxed.
Domyślam się, że kompilator widzi zwracany typ wyrażenia jako Object.
źródło