Restituisce null come int consentito con l'operatore ternario ma non se istruzione


186

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 temp()metodo non genera alcun errore del compilatore anche se è il tipo restituito della funzione inte stiamo provando a restituire il valore null(tramite l'istruzione return true ? null : 0;). Quando compilato, questo ovviamente causa l'eccezione di runtime NullPointerException.

Tuttavia, sembra che la stessa cosa è sbagliato se noi rappresentiamo l'operatore ternario con una ifdichiarazione (come nel same()metodo), che fa emettere un errore di compilazione! Perché?


6
Inoltre, int foo = (true ? null : 0)ed new Integer(null)entrambi compilano bene, il secondo è la forma esplicita di autoboxing.
Izkata,

2
@Izkata il problema qui è per me capire perché il compilatore sta cercando di autobox nullper Integer... Sarebbe come "indovinare" per me o "far funzionare le cose" ...
Marsellus Wallace

1
... Huhm, pensavo di avere una risposta lì, poiché il costruttore Integer (ciò che i documenti che ho trovato dicono è usato per l'autoboxing) è autorizzato a prendere una stringa come argomento (che può essere nullo). Tuttavia, dicono anche che il costruttore agisce in modo identico al metodo parseInt (), che genererebbe un NumberFormatException dopo essere stato superato un null ...
Izkata

3
@Izkata: l'argomento String c'tor per Integer non è un'istruzione autoboxing. Una stringa non può essere autoboxata su un numero intero. (La funzione Integer foo() { return "1"; }non verrà compilata.)
Ted Hopp,

5
Bene, ho imparato qualcosa di nuovo sull'operatore ternario!
Oksayt,

Risposte:


118

Il compilatore interpreta nullcome riferimento null a un Integer, applica le regole di autoboxing / unboxing per l'operatore condizionale (come descritto in Java Language Specification, 15.25 ) e si sposta felicemente su. Questo genererà un NullPointerExceptiontempo di esecuzione, che puoi confermare provandolo.


Dato il collegamento alle specifiche del linguaggio Java che hai pubblicato, quale punto pensi venga eseguito nel caso della domanda precedente? L'ultimo (visto che sto ancora cercando di capire capture conversione lub(T1,T2)) ?? Inoltre, è davvero possibile applicare la boxe a un valore nullo? Non sarebbe come "indovinare" ??
Marsellus Wallace,

´ @ Gevorg Un puntatore nullo è un puntatore valido per ogni possibile oggetto, quindi non può succedere nulla di male. Il compilatore presuppone solo che null sia un numero intero che può quindi autoboxare su int.
Voo,

1
@Gevorg - Vedi il commento di nowaq e la mia risposta al suo post. Penso che abbia scelto la clausola corretta. lub(T1,T2)è il tipo di riferimento più specifico in comune nella gerarchia dei tipi di T1 e T2. (Entrambi condividono almeno l'Oggetto, quindi esiste sempre un tipo di riferimento più specifico.)
Ted Hopp,

8
@Gevorg - nullnon è confezionato in un numero intero, viene interpretata come un riferimento a un numero intero (riferimento null, ma che non è un problema). Nessun oggetto intero viene creato dal valore null, quindi non esiste alcun motivo per NumberFormatException.
Ted Hopp,

1
@Gevorg - Se guardi le regole per la conversione del pugilato e le applichi anull (che non è un tipo numerico primitivo), la clausola applicabile è "Se p è un valore di qualsiasi altro tipo, la conversione di boxe è equivalente a una conversione di identità ". Quindi la conversione del pugilato nullai Integerrendimenti null, senza invocare alcun Integercostruttore.
Ted Hopp,

40

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

Per il secondo caso, l'espressione nullè di speciale tipo nullo veda , per cui il codice return nullrende tipo non corrispondente.


2
Presumo che questo sia legato al pugilato automatico? Presumibilmente il primo ritorno non si sarebbe compilato prima di Java 5, giusto?
Michael McGowan,

@Michael che sembra essere il caso se imposti il ​​livello di conformità di Eclipse su pre-5.
Jonathon Faust,

@Michael: questo sembra decisamente auto-boxe (sono abbastanza nuovo in Java e non posso fare affermazioni più definite - scusate).
Vlad

1
@Vlad come sarebbe la fine compilatore fino interpretare true ? null : 0come Integer? Autoboxing 0prima ??
Marsellus Wallace,

1
@Gevorg: guarda qui : in caso contrario, il secondo e il terzo operando sono rispettivamente di tipo S1 e S2. Sia T1 il tipo che risulta dall'applicazione della conversione di boxe a S1 e T2 sia il tipo risultante dall'applicazione della conversione di boxe a S2. e il seguente testo.
Vlad

32

In realtà, è tutto spiegato nelle specifiche 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), questo è il tipo dell'espressione condizionale.

Pertanto il "null" nel tuo (true ? null : 0)ottiene un tipo int e quindi viene autoboxato su Integer.

Prova qualcosa del genere per verificarlo (true ? null : null)e otterrai l'errore del compilatore.


3
Ma quella clausola delle regole non si applica: il secondo e il terzo operando non hanno lo stesso tipo.
Ted Hopp,

1
Quindi la risposta sembra essere nella seguente dichiarazione:> Altrimenti, il secondo e il terzo operando sono rispettivamente dei tipi S1 e S2. Sia T1 il tipo che risulta dall'applicazione della conversione di boxe a S1 e T2 sia il tipo risultante dall'applicazione della conversione di boxe a S2. Il tipo dell'espressione condizionale è il risultato dell'applicazione della conversione della cattura (§5.1.10) in lub (T1, T2) (§15.12.2.7).
ora l'

Penso che sia la clausola applicabile. Quindi tenta di applicare l'auto-unboxing per restituire un intvalore dalla funzione, che provoca un NPE.
Ted Hopp,

@nowaq L'ho pensato anch'io. Tuttavia, se si tenta di casella in modo esplicito nulla Integercon new Integer(null);"Let T1 essere il tipo che i risultati di applicazione di conversione di boxe a S1 ..." si otterrebbe un NumberFormatExceptione questo non è il caso ...
Marsellus Wallace

@Gevorg Penso che dato che si verifica un'eccezione quando si fa il pugilato non otteniamo NESSUN risultato qui. Il compilatore è solo obbligato a generare codice che segue la definizione che fa - otteniamo l'eccezione prima di aver finito.
Voo

25

Nel caso della ifdichiarazione, il nullriferimento non viene trattato come Integerriferimento perché non partecipa a un'espressione che lo costringe ad essere interpretato come tale. Pertanto, l'errore può essere facilmente individuato in fase di compilazione perché è più chiaramente un tipo errore di .

Per quanto riguarda l'operatore condizionale, la specifica del linguaggio Java §15.25 "Operatore condizionale ? :" risponde bene alle regole per l'applicazione della conversione del tipo:

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

    Non si applica perché nullnon lo è 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é nullintè booleanBoolean.

  • Se uno dei secondi e terzi operandi è di tipo null e il tipo di altro è un tipo di riferimento, il tipo dell'espressione condizionale è quel tipo di riferimento.

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

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

    Si applica: nullè trattato come convertibile in un tipo numerico ed è definito in §5.1. 8 "Conversione di Unboxing" per lanciare a NullPointerException.

Se 0viene eseguito il box automatico, Integeril compilatore sta eseguendo l'ultimo caso delle "regole dell'operatore ternario" come descritto nella specifica del linguaggio Java. Se questo è vero, allora è difficile per me credere che passerebbe al caso 3 delle stesse regole che hanno un tipo nullo e di riferimento che rende il valore di ritorno dell'operatore ternario come tipo di riferimento (intero). .
Marsellus Wallace

@Gevorg - Perché è difficile credere che l'operatore ternario stia restituendo un Integer? Questo è esattamente ciò che sta accadendo; l'NPE viene generato tentando di decomprimere il valore dell'espressione per restituire un intdalla funzione. Modificare la funzione per restituire un Integere tornerà nullsenza alcun problema.
Ted Hopp,

2
@TedHopp: Gevorg stava rispondendo a una precedente revisione della mia risposta, che era errata. Dovresti ignorare la discrepanza.
Jon Purdy,

@JonPurdy "Si dice che un tipo è convertibile in un tipo numerico se è un tipo numerico, oppure è un tipo di riferimento che può essere convertito in un tipo numerico mediante conversione unboxing" e non credo che nullrientri in questa categoria . Inoltre, passeremmo al passaggio "Altrimenti, viene applicata la promozione numerica binaria (§5.6.2) ... Nota che la promozione numerica binaria esegue la conversione unboxing (§5.1.8) ..." per determinare il tipo di ritorno. Ma la conversione unboxing genererebbe un NPE e questo accade solo in fase di esecuzione e non durante il tentativo di determinare il tipo di operatore ternario. Sono ancora confuso ..
Marsellus Wallace,

@Gevorg: l'Unboxing avviene in fase di esecuzione. Il nullviene trattato come se avesse tipo int, ma in realtà è equivalente a throw new NullPointerException(), questo è tutto.
Jon Purdy,

11

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 dai tipi reali / reali del secondo o terzo parametro. A seconda di diversi fattori, il tipo di operatore ternario viene determinato in diversi modi, come illustrato nella specifica del linguaggio Java 15.26

Nella domanda sopra dovremmo considerare l'ultimo caso:

Altrimenti, il secondo e il terzo operando sono rispettivamente di tipo S1 e S2 . Sia T1 il tipo risultante dall'applicazione della conversione del pugilato a S1 e T2 sia il tipo risultante dall'applicazione della conversione del pugilato a S2 . Il tipo dell'espressione condizionale è il risultato dell'applicazione della conversione della cattura (§5.1.10) in 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 parole povere e dopo un'estrema semplificazione possiamo descrivere il processo come il calcolo della "Minima Superclasse Comune" (sì, pensa alla LCM) del secondo e terzo parametro. Questo ci darà l'operatore "tipo" ternario. Ancora una volta, ciò che ho appena detto è un'estrema semplificazione (considera 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.Datedato che è la "Superclass meno comune" per Timestamp/Time coppia .

Poiché nullpuò essere autoboxato su qualsiasi cosa, la "Superclass comune minima" è la Integerclasse e questo sarà il tipo restituito dell'espressione condizionale (operatore ternario) sopra. Il valore restituito sarà quindi un puntatore null di tipoInteger e questo è ciò che verrà restituito dall'operatore ternario.

In fase di esecuzione, quando la Java Virtual Machine unboxes l' Integeruna NullPointerExceptionè gettato. Ciò accade perché JVM tenta di richiamare la funzione null.intValue(), dove nullè il risultato del box automatico.

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 : param2il compilatore dovrebbe determinare immediatamente 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 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 nullvalore può essere inscatolato / autoboxato su qualsiasi cosa. Parlando della classe Integer, il boxing esplicito consiste nell'invocare il new Integer(...)costruttore o forse il Integer.valueOf(int i);(ho trovato questa versione da qualche parte). Il primo lancerebbe un NumberFormatException(e questo non accade) mentre il secondo non avrebbe senso dal momento che un intnon può essere null...


1
Il nullcodice originale dell'OP non è inscatolato. Il modo in cui funziona è: il compilatore presuppone che nullsia un riferimento a un intero. Usando le regole per i tipi di espressione ternaria, decide che l'intera espressione è un'espressione Integer. Quindi genera il codice per la casella automatica 1(nel caso in cui la condizione venga valutata false). Durante l'esecuzione, la condizione viene valutata in truemodo da valutare l'espressione null. Quando si tenta di restituire un intdalla funzione, nullviene deselezionato. Che quindi genera un NPE. (Il compilatore potrebbe ottimizzare gran parte di tutto questo.)
Ted Hopp,

4

In realtà, nel primo caso l'espressione può essere valutata, poiché il compilatore lo sa, che deve essere valutata come una Integer, tuttavia nel secondo caso il tipo del valore restituito ( null) non può essere determinato, quindi non può essere compilato. Se Integerlo esegui, il codice verrà compilato.


2
private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of unboxing 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
}

0

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 autobox.

La mia ipotesi è che il compilatore sta vedendo il tipo restituito dell'espressione come Object.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.