Tricky operatore ternario in Java – autoboxing

Diamo un’occhiata al semplice codice Java nel seguente frammento:

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()); } } 

In questo codice Java più semplice, il metodo temp() non genera errori del compilatore anche se il tipo restituito della funzione è int , e stiamo cercando di restituire il valore null (tramite l’istruzione return true ? null : 0; ). Quando viene compilato, ciò ovviamente causa l’eccezione di NullPointerException .

Tuttavia, sembra che la stessa cosa sia sbagliata se rappresentiamo l’operatore ternario con un’istruzione if (come nel metodo same() ), che genera un errore in fase di compilazione! Perché?

Il compilatore interpreta null come riferimento null a un numero Integer , applica le regole di autoboxing / unboxing per l’operatore condizionale (come descritto in Java Language Specification, 15.25 ) e si muove felicemente. Questo genererà una NullPointerException in fase di esecuzione, che puoi confermare provandolo.

Penso che il compilatore Java interpreti il true ? null : 0 true ? null : 0 come espressione Integer , che può essere convertita implicitamente in int , possibilmente dando NullPointerException .

Per il secondo caso, l’espressione null è del tipo null speciale see , quindi il codice return null rende il tipo non corrispondente.

In realtà, è tutto spiegato nella specifica del linguaggio Java .

Il tipo di un’espressione condizionale è determinato come segue:

  • Se il secondo e il terzo operando hanno lo stesso tipo (che può essere il tipo null), allora questo è il tipo di espressione condizionale.

Quindi il “nulla” nel tuo (true ? null : 0) ottiene un tipo int e quindi viene automaticamente inserito in numero intero.

Prova qualcosa del genere per verificare questo (true ? null : null) e otterrai l’errore del compilatore.

Nel caso dell’istruzione if , il riferimento null non viene considerato come riferimento Integer perché non sta partecipando a un’espressione che lo obbliga ad essere interpretato come tale. Quindi l’errore può essere facilmente catturato in fase di compilazione perché è più chiaramente un errore di tipo .

Per quanto riguarda l’operatore condizionale, la specifica della lingua Java §15.25 “Operatore condizionale ? : ? : “Risponde bene nelle regole per come viene applicata la conversione dei tipi:

  • Se il secondo e il terzo operando hanno lo stesso tipo (che può essere il tipo null), allora questo è il tipo di espressione condizionale.

    Non si applica perché null non è int .


  • Se uno dei secondi e terzi operandi è di tipo booleano e il tipo dell’altro è di tipo Booleano, il tipo dell’espressione condizionale è booleano.

    Non si applica perché né nè né int sono boolean o Boolean .


  • Se uno tra il secondo e il terzo operando è di tipo nullo e il tipo dell’altro è un tipo di riferimento, il tipo di espressione condizionale è quel tipo di riferimento.

    Non si applica perché null è di tipo null, ma int non è un tipo di riferimento.


  • Altrimenti, se il secondo e il terzo operando hanno tipi convertibili (§5.1.8) in tipi numerici, ci sono diversi casi: […]

    Si applica: il valore null viene considerato convertibile in un tipo numerico ed è definito in §5.1.8 “Conversione unboxing” per generare una NullPointerException .

La prima cosa da tenere a mente è che gli operatori ternari Java hanno un “tipo”, e che questo è ciò che il compilatore determinerà e considererà indipendentemente dal tipo effettivo / reale del secondo o terzo parametro. In base a diversi fattori, il tipo di operatore ternario viene determinato in modi diversi, come illustrato nella Specifica del linguaggio Java 15.26

Nella domanda di cui sopra dovremmo considerare l’ultimo caso:

Altrimenti, il secondo e il terzo operando sono rispettivamente dei tipi S1 e S2 . Sia T1 il tipo risultante dall’applicazione della conversione di boxing in S1 , e sia T2 il tipo risultante dall’applicazione della conversione di boxing in S2 . Il tipo di espressione condizionale è il risultato dell’applicazione della conversione di cattura (§5.1.10) a lub (T1, T2) (§15.12.2.7).

Questo è di gran lunga il caso più complesso dopo aver dato un’occhiata all’applicazione della conversione di cattura (§5.1.10) e soprattutto a lub (T1, T2) .

In inglese semplice e dopo un’estrema semplificazione possiamo descrivere il processo come il calcolo della “superclass minima comune” (si, pensa al LCM) del secondo e del terzo parametro. Questo ci darà il “tipo” dell’operatore ternario. Ancora una volta, ciò che ho appena detto è una semplificazione estrema (considerate le classi che implementano più interfacce comuni).

Ad esempio, se provi quanto segue:

 long millis = System.currentTimeMillis(); return(true ? new java.sql.Timestamp(millis) : new java.sql.Time(millis)); 

Noterai che il tipo risultante dell’espressione condizionale è java.util.Date poiché è la “Least Common Superclass” per la coppia Timestamp / Time .

Dato che null può essere autoboxato a qualsiasi cosa, la “Least Common Superclass” è la class Integer e questo sarà il tipo di ritorno dell’espressione condizionale (operatore ternario) sopra. Il valore di ritorno sarà quindi un puntatore nullo di tipo Integer ed è ciò che verrà restituito dall’operatore ternario.

In fase di runtime, quando Java Virtual Machine cancella il numero Integer viene generata una NullPointerException . Ciò accade perché la JVM tenta di richiamare la funzione null.intValue() , dove null è il risultato della autoboxing.

Secondo me (e poiché la mia opinione non è nella specifica del linguaggio Java, molte persone lo troveranno comunque sbagliato) il compilatore fa un pessimo lavoro nel valutare l’espressione nella tua domanda. Dato che hai scritto true ? param1 : param2 true ? param1 : param2 il compilatore dovrebbe determinare subito che il primo parametro – null – verrà restituito e dovrebbe generare un errore del compilatore. Questo è in qualche modo simile a quando scrivi while(true){} etc... e il compilatore si lamenta del codice sotto il ciclo e lo contrassegna con le Unreachable Statements .

Il tuo secondo caso è piuttosto semplice e questa risposta è già troppo lunga …;)

CORREZIONE:

Dopo un’altra analisi, credo di aver sbagliato a dire che un valore null può essere inscatolato / autoboxato a qualsiasi cosa. Parlando della class Integer, il pugilato esplicito consiste nel richiamo del new Integer(...) costruttore di new Integer(...) o forse Integer.valueOf(int i); (Ho trovato questa versione da qualche parte). Il primo lancia una NumberFormatException (e questo non succede) mentre il secondo non avrebbe senso dal momento che un int non può essere null

In realtà, nel primo caso l’espressione può essere valutata, dal momento che il compilatore sa, che deve essere valutata come un Integer , tuttavia nel secondo caso il tipo del valore restituito ( null ) non può essere determinato, quindi non può essere compilato. Se lo lanci su Integer , il codice verrà compilato.

 private int temp() { if (true) { Integer x = null; return x;// since that is fine because of auto-boxing then the returned value could be null //in other words I can say x could be null or new Integer(intValue) or a intValue } return (true ? null : 0); //this will be prefectly legal null would be refrence to Integer. The concept is one the returned //value can be Integer // then null is accepted to be a variable (-refrence variable-) of Integer } 

Cosa ne pensi di questo:

 public class ConditionalExpressionType { public static void main(String[] args) { String s = ""; s += (true ? 1 : "") instanceof Integer; System.out.println(s); String t = ""; t += (!true ? 1 : "") instanceof String; System.out.println(t); } } 

L’output è vero, vero.

Il colore Eclipse codifica il 1 nell’espressione condizionale come autoboxed.

La mia ipotesi è che il compilatore sta vedendo il tipo di ritorno dell’espressione come Oggetto.