Prima di Java8, in quasi tutti i casi & dagger;, il tipo di un'espressione è creato dal basso verso l'alto, interamente in base ai tipi di sottoespressioni; non dipende dal contesto. Questo è bello e semplice, e il codice è facile da capire; ad esempio, la risoluzione del sovraccarico dipende dai tipi di argomenti, che vengono risolti indipendentemente dal contesto di richiamo del metodo.
(& pugnale; L'unica eccezione che so è jls#15.12.2.8)
Dato un'espressione condizionale in forma di ?int:Integer
, spec deve definire un tipo fisso per esso senza riguardo al contesto. È stato scelto il tipo int
, che è presumibilmente migliore nella maggior parte dei casi d'uso. Ovviamente, è anche la fonte di NPE da unboxing.
In Java8, le informazioni sul tipo contestuale possono essere utilizzate per l'inferenza di tipo. Questo è conveniente per molti casi; ma introduce anche confusione, poiché potrebbero esserci due direzioni per risolvere il tipo di un'espressione. Fortunatamente, alcune espressioni sono ancora autonome; i loro tipi sono indipendenti dal contesto.
con.t espressioni condizionali, non vogliamo che quelli semplici come false?0:1
siano dipendenti dal contesto; i loro tipi sono evidenti. D'altra parte, vogliamo l'inferenza di tipo contestuale su espressioni condizionali più complicate, come false?f():g()
dove f/g()
richiede l'inferenza di tipo.
La linea è stata tracciata tra i tipi primitivi e di riferimento. In op1?op2:op3
, se sia op2
e op3
sono tipi "chiari" primitivi (o versioni in scatola di), viene considerato come autonomo. Quoting Dan Smith -
classifichiamo espressioni condizionali qui al fine di migliorare le regole di battitura di condizionali di riferimento (15.25.3), preservando il comportamento attuale dei condizionali booleani e numerici. Se provassimo a trattare tutti i condizionali in modo uniforme, ci sarebbero una varietà di modifiche incompatibili non volute, comprese le modifiche alla risoluzione di sovraccarico e al comportamento di boxe/unboxing.
Nel tuo caso
Integer x = false ? 3 : false ? 4 : null;
dal false?4:null
è "chiaramente" un Integer
, l'espressione genitore è in forma di ?:int:Integer
(?); questo è un caso primitivo, e il suo comportamento è mantenuto compatibile con java7, quindi, NPE.
Ho messo le virgolette su "chiaramente" perché questa è la mia comprensione intuitiva; Non sono sicuro delle specifiche formali. Diamo un'occhiata a questo esempio
static <T> T f1(){ return null; }
--
Integer x = false ? 3 : false ? f1() : null;
Compila! e non c'è NPE in fase di esecuzione! Non so come seguire le specifiche su questo caso. Posso immaginare che il compilatore faccia probabilmente i seguenti passi:
1) la sottoespressione false?f1():null
non è "chiaramente" un tipo primitivo (in scatola); il suo tipo è ancora sconosciuto
2) quindi, l'espressione genitore è classificata come "espressione condizionale di riferimento", che appare in un contesto di assegnazione.
3) il tipo di destinazione Integer
viene applicato agli operandi, ed eventualmente f1()
, che viene poi dedotta per tornare Integer
4) Tuttavia, non possiamo tornare indietro a ri-classificare l'espressione condizionale come ?int:Integer
.
Sembra ragionevole. Tuttavia, cosa succede se si specifica esplicitamente l'argomento type su f1()
? Teoria
Integer x = false ? 3 : false ? Test.<Integer>f1() : null;
(A) - questo non dovrebbe alterare la semantica del programma, perché è lo stesso tipo di argomento che sarebbe stato dedotto. Non dovremmo vedere NPE in fase di runtime.
Teoria (B) - non esiste un'inferenza di tipo; il tipo di sottoespressione è chiaramente Integer
, quindi questo dovrebbe essere classificato come caso primitivo e dovremmo vedere NPE in fase di esecuzione.
Credo in (B); tuttavia, javac (8u60) fa (A). Non capisco perché.
Spingendo questa osservazione ad un livello esilarante
class MyList1 extends ArrayList<Integer>
{
//inherit public Integer get(int index)
}
class MyList2 extends ArrayList<Integer>
{
@Override public Integer get(int index)
{
return super.get(0);
}
}
MyList1 myList1 = new MyList1();
MyList2 myList2 = new MyList2();
Integer x1 = false ? 3 : false ? myList1.get(0) : null; // no NPE
Integer x2 = false ? 3 : false ? myList2.get(0) : null; // NPE !!!
Ciò non ha alcun senso; qualcosa di veramente funky sta succedendo all'interno di javac.
(vedi anche Java autoboxing and ternary operator madness)
Look up autoboxing. –
Accesso puntatore nullo: questa espressione di tipo Integer è nullo ma richiede l'auto-unboxing –
Vedere anche queste domande: [Autoboxing Java e follia ternaria] (http://stackoverflow.com/questions/25417438/java-autoboxing-and-ternary -operator-madness) e [NPE tramite auto-boxing del ternario Java] (http://stackoverflow.com/questions/12763983/nullpointerexception-through-auto-boxing-behavior-of-java-ternary-operator). –