Ternärer Java-Operator vs if / else in <JDK8-Kompatibilität

112

Kürzlich lese ich den Quellcode von Spring Framework. Etwas, das ich nicht verstehen kann, geht hier:

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

Diese Methode ist Mitglied der Klasse org.springframework.core.MethodParameter. Der Code ist leicht zu verstehen, während die Kommentare schwierig sind.

ANMERKUNG: Kein ternärer Ausdruck, um die JDK <8-Kompatibilität beizubehalten, selbst wenn der JDK 8-Compiler verwendet wird (möglicherweise java.lang.reflect.Executableals allgemeiner Typ ausgewählt, da diese neue Basisklasse bei älteren JDKs nicht verfügbar ist)

Was ist der Unterschied zwischen der Verwendung des ternären Ausdrucks und der Verwendung des if...else...Konstrukts in diesem Zusammenhang?

jddxf
quelle

Antworten:

102

Wenn Sie über die Art der Operanden nachdenken, wird das Problem offensichtlicher:

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

hat als Typ den spezialisiertesten gemeinsamen Typ beider Operanden, dh den spezialisiertesten Typ, der beiden this.methodund gemeinsam ist this.constructor.

In Java 7 ist dies java.lang.reflect.Memberjedoch so, dass die Java 8-Klassenbibliothek einen neuen Typ einführt, java.lang.reflect.Executableder spezialisierter ist als der generische Member. Daher ist bei einer Java 8-Klassenbibliothek der Ergebnistyp des ternären Ausdrucks Executableeher als Member.

Einige (Vorabversionen) Versionen des Java 8-Compilers scheinen Executablebeim Kompilieren des ternären Operators einen expliziten Verweis auf innerhalb des generierten Codes erstellt zu haben . Dies würde eine Klassenladung auslösen und somit ClassNotFoundExceptionzur Laufzeit wiederum eine, wenn mit einer Klassenbibliothek <JDK 8 ausgeführt wird, weilExecutable nur für JDK ≥ 8 vorhanden ist.

Wie Tagir Valeev in dieser Antwort feststellte , handelt es sich tatsächlich um einen Fehler in Vorabversionen von JDK 8, der inzwischen behoben wurde. Daher sind sowohl die if-elseProblemumgehung als auch der erläuternde Kommentar veraltet.

Zusätzlicher Hinweis: Man könnte zu dem Schluss kommen, dass dieser Compiler-Fehler vor Java 8 vorhanden war. Der von OpenJDK 7 für das Ternär generierte Bytecode ist jedoch der gleiche wie der von OpenJDK 8 generierte Bytecode. Tatsächlich ist der Typ des Ausdruck wird zur Laufzeit völlig unerwähnt gelassen, der Code ist wirklich nur Test, Verzweigung, Laden, Zurückgeben, ohne dass zusätzliche Überprüfungen stattfinden. Seien Sie also versichert, dass dies kein Problem mehr ist und tatsächlich ein vorübergehendes Problem während der Entwicklung von Java 8 zu sein scheint.

dhke
quelle
1
Wie kann dann mit JDK 1.8 kompilierter Code unter JDK 1.7 ausgeführt werden? Ich habe gewusst, dass mit JDK niedrigerer Version kompilierter Code problemlos mit JDK höherer Version ausgeführt werden kann. Umgekehrt?
jddxf
1
@jddxf Alles ist in Ordnung, solange Sie die richtige Klassendateiversion angegeben haben und keine Funktionen verwenden, die in späteren Versionen nicht verfügbar sind. Das Problem muss jedoch auftreten, wenn eine solche Verwendung implizit wie in diesem Fall erfolgt.
Dhke
13
@jddxf, benutze -source / -target javac options
Tagir Valeev
1
Vielen Dank an alle, besonders an Dhke und Tagir Valeev, die eine gründliche Erklärung gegeben haben
jddxf
30

Dies wurde in einem ziemlich alten Commit am 3. Mai 2013 eingeführt, fast ein Jahr vor der offiziellen Veröffentlichung von JDK-8. Der Compiler befand sich zu dieser Zeit in einer intensiven Entwicklung, so dass solche Kompatibilitätsprobleme auftreten konnten. Ich denke, das Spring-Team hat gerade den JDK-8-Build getestet und versucht, Probleme zu beheben, obwohl es sich tatsächlich um Compiler-Probleme handelt. Mit der offiziellen Veröffentlichung von JDK-8 wurde dies irrelevant. Jetzt funktioniert der ternäre Operator in diesem Code wie erwartet Executableeinwandfrei (es ist kein Verweis auf die Klasse in der kompilierten .class-Datei vorhanden).

Derzeit erscheinen ähnliche Dinge in JDK-9: Ein Teil des Codes, der in JDK-8 gut kompiliert werden kann, ist mit JDK-9 javac fehlgeschlagen. Ich denke, die meisten dieser Probleme werden bis zur Veröffentlichung behoben sein.

Tagir Valeev
quelle
2
+1. War dies ein Fehler im frühen Compiler? Verstieß dieses Verhalten, wo es erwähnt wurde Executable, gegen einen Aspekt der Spezifikation? Oder hat Oracle nur erkannt, dass sie dieses Verhalten so ändern können, dass es immer noch der Spezifikation entspricht und ohne die Abwärtskompatibilität zu beeinträchtigen?
Ruakh
2
@ruakh, ich denke es war der Fehler. In Bytecode (entweder in Java-8 oder früher) ist es völlig unnötig, explizit umzuwandeln, um Executabledazwischen zu tippen. In Java-8 hat sich das Konzept der Inferenz von Ausdruckstypen drastisch geändert, und dieser Teil wurde vollständig neu geschrieben. Daher ist es nicht so überraschend, dass frühe Implementierungen Fehler aufwiesen.
Tagir Valeev
7

Der Hauptunterschied besteht darin, dass ein if elseBlock eine Anweisung ist, während der ternäre Block ( in Java häufiger als bedingter Operator bezeichnet) ein Ausdruck ist .

Eine Anweisung kann returnauf einigen Steuerpfaden Dinge wie den Aufrufer tun . Ein Ausdruck kann in einer Aufgabe verwendet werden:

int n = condition ? 3 : 2;

Daher müssen die beiden Ausdrücke im Ternär nach der Bedingung auf denselben Typ erzwungen werden können. Dies kann in Java einige seltsame Effekte verursachen, insbesondere beim automatischen Boxen und beim automatischen Referenzcasting. Darauf bezieht sich der Kommentar in Ihrem veröffentlichten Code. Der Zwang der Ausdrücke in Ihrem Fall wäre ein java.lang.reflect.ExecutableTyp (da dies der spezialisierteste Typ ist ), der in älteren Java-Versionen nicht vorhanden ist.

Stilistisch sollten Sie einen if elseBlock verwenden, wenn der Code anweisungsähnlich ist, und einen ternären, wenn er ausdrucksähnlich ist.

Natürlich können Sie einen if elseBlock wie einen Ausdruck verhalten lassen, wenn Sie eine Lambda-Funktion verwenden.

Bathseba
quelle
6

Der Rückgabewerttyp in einem ternären Ausdruck wird von übergeordneten Klassen beeinflusst, die sich wie in Java 8 beschrieben geändert haben.

Schwer zu verstehen, warum eine Besetzung nicht geschrieben werden konnte.

Marquis von Lorne
quelle