Operatore ternario Java vs if / else nella compatibilità <JDK8


113

Recentemente sto leggendo il codice sorgente di Spring Framework. Qualcosa che non riesco a capire va qui:

public Member getMember() {
    // NOTE: no ternary expression to retain JDK <8 compatibility even when using
    // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
    // as common type, with that new base class not available on older JDKs)
    if (this.method != null) {
        return this.method;
    }
    else {
        return this.constructor;
    }
}

Questo metodo è un membro della classe org.springframework.core.MethodParameter. Il codice è facile da capire mentre i commenti sono difficili.

NOTA: nessuna espressione ternaria per mantenere la compatibilità JDK <8 anche quando si utilizza il compilatore JDK 8 (potenzialmente selezionando java.lang.reflect.Executablecome tipo comune, con quella nuova classe base non disponibile su JDK precedenti)

Qual è la differenza tra l'uso di espressioni ternarie e l'uso di if...else...costrutti in questo contesto?

Risposte:


103

Quando si pensa al tipo di operandi, il problema diventa più evidente:

this.method != null ? this.method : this.constructor

ha come tipo il tipo comune più specializzato di entrambi gli operandi, cioè il tipo più specializzato comune a entrambi this.methode this.constructor.

In Java 7 questo è java.lang.reflect.Member, tuttavia la libreria di classi Java 8 introduce un nuovo tipo java.lang.reflect.Executableche è più specializzato del generico Member. Quindi con una libreria di classi Java 8 il tipo di risultato dell'espressione ternaria è Executablepiuttosto che Member.

Alcune versioni (pre-release) del compilatore Java 8 sembrano aver prodotto un riferimento esplicito al Executablecodice generato all'interno durante la compilazione dell'operatore ternario. Ciò attiverebbe un caricamento di classe, e quindi a sua volta ClassNotFoundExceptionin fase di esecuzione quando viene eseguito con una libreria di classi <JDK 8, perché Executableesiste solo per JDK ≥ 8.

Come notato da Tagir Valeev in questa risposta , questo è in realtà un bug nelle versioni pre-rilascio di JDK 8 e da allora è stato risolto, quindi sia la if-elsesoluzione alternativa che il commento esplicativo sono ora obsoleti.

Nota aggiuntiva: si potrebbe giungere alla conclusione che questo bug del compilatore fosse presente prima di Java 8. Tuttavia, il byte code generato per il ternario da OpenJDK 7 è lo stesso del byte code generato da OpenJDK 8. Infatti, il tipo di L'espressione non viene menzionata completamente in fase di esecuzione, il codice è in realtà solo test, branch, load, return senza ulteriori controlli in corso. Quindi stai certo che questo non è un problema (più) e anzi sembra essere stato un problema temporaneo durante lo sviluppo di Java 8.


1
Allora come può il codice compilato con JDK 1.8 funzionare su JDK 1.7. Ho saputo che il codice compilato con JDK di versione inferiore può essere eseguito su JDK di versione superiore senza problemi.
jddxf

1
@jddxf Va tutto bene fintanto che hai specificato la versione del file di classe corretta e non usi alcuna funzionalità che non è disponibile nelle versioni successive. Il problema è destinato a verificarsi, tuttavia, se tale uso avviene implicitamente come in questo caso.
dal

13
@jddxf, usa le opzioni -source / -target javac
Tagir Valeev

1
Grazie a tutti, in particolare dhke e Tagir Valeev, che hanno fornito una spiegazione approfondita
jddxf

30

Questo è stato introdotto in un commit piuttosto vecchio il 3 maggio 2013, quasi un anno prima del rilascio ufficiale di JDK-8. Il compilatore era in forte sviluppo a quei tempi, quindi potrebbero verificarsi problemi di compatibilità. Immagino che il team di Spring abbia appena testato la build JDK-8 e abbia cercato di risolvere i problemi, anche se in realtà sono problemi del compilatore. Con il rilascio ufficiale di JDK-8 questo è diventato irrilevante. Ora l'operatore ternario in questo codice funziona correttamente come previsto (non Executableè presente alcun riferimento alla classe nel file .class compilato).

Attualmente cose simili appaiono in JDK-9: un po 'di codice che può essere ben compilato in JDK-8 fallisce con JDK-9 javac. Immagino che la maggior parte di questi problemi verrà risolta fino al rilascio.


2
+1. Quindi, era questo un bug nel primo compilatore? Quel comportamento, dove si riferiva Executable, era in violazione di qualche aspetto delle specifiche? O è solo che Oracle si è reso conto che potevano cambiare questo comportamento in un modo che sarebbe comunque conforme alle specifiche e senza rompere la compatibilità con le versioni precedenti?
ruakh

2
@ruakh, immagino sia stato il bug. In bytecode (in Java-8 o versioni precedenti) non è assolutamente necessario eseguire il cast esplicito per Executabledigitare nel mezzo. In Java-8 il concetto di inferenza del tipo di espressione è cambiato drasticamente e questa parte è stata completamente riscritta, quindi non è così sorprendente che le prime implementazioni contenessero bug.
Tagir Valeev

7

La differenza principale è che un if elseblocco è un'istruzione mentre il ternario (più spesso noto come operatore condizionale in Java) è un'espressione .

Una dichiarazione può fare cose come returnal chiamante su alcuni dei percorsi di controllo. Un'espressione può essere utilizzata in un incarico:

int n = condition ? 3 : 2;

Quindi le due espressioni nel ternario dopo la condizione devono essere coercibili allo stesso tipo. Ciò può causare alcuni effetti strani in Java, in particolare con l'auto-boxing e il casting automatico dei riferimenti: questo è ciò a cui si riferisce il commento nel codice pubblicato. La coercizione delle espressioni nel tuo caso sarebbe un java.lang.reflect.Executabletipo (poiché è il tipo più specializzato ) e che non esiste nelle versioni precedenti di Java.

Stilisticamente dovresti usare un if elseblocco se il codice è simile a un'istruzione e un ternario se è simile a un'espressione.

Ovviamente, puoi fare in modo che un if elseblocco si comporti come un'espressione se usi una funzione lambda.


6

Il tipo di valore restituito in un'espressione ternaria è influenzato dalle classi padre, che sono cambiate come descritto in Java 8.

Difficile capire perché non sia stato possibile scrivere un cast.

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.