webentwicklung-frage-antwort-db.com.de

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

Kürzlich lese ich den Quellcode von Spring Framework. Was ich nicht verstehen kann, ist 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 ein Member der Klasse org.springframework.core.MethodParameter. Der Code ist leicht zu verstehen, während die Kommentare schwierig sind.

HINWEIS: Kein ternärer Ausdruck zur Beibehaltung der JDK <8-Kompatibilität, auch wenn der JDK 8-Compiler verwendet wird (möglicherweise Auswahl von Java.lang.reflect.Executable als gebräuchlicher Typ, wobei diese neue Basisklasse auf älteren JDKs nicht verfügbar ist)

Was ist der Unterschied zwischen der Verwendung eines ternären Ausdrucks und der Verwendung von if...else... in diesem Zusammenhang konstruieren?

112
jddxf

Wenn Sie über den Typ der Operanden nachdenken, wird das Problem offensichtlicher:

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

hat als Typ den spezialisiertesten gemeinsamen Typ beider Operanden, d. h. den spezialisiertesten gemeinsamen Typ für beide this.method und this.constructor.

In Java 7 ist dies Java.lang.reflect.Member , jedoch führt die Klassenbibliothek Java 8 einen neuen Typ ein Java.lang.reflect.Executable welches spezialisierter ist als das generische Member. Daher ist bei einer Klassenbibliothek Java 8) der Ergebnistyp des ternären Ausdrucks Executable und nicht Member.

Einige (Vorab-) Versionen des Java 8-Compilers scheinen beim Kompilieren des ternären Operators einen expliziten Verweis auf Executable innerhalb des generierten Codes erzeugt zu haben. Dies würde ein Laden der Klasse auslösen , und somit wiederum ein ClassNotFoundException zur Laufzeit, wenn mit einer Klassenbibliothek <JDK 8 ausgeführt wird, da Executable nur für JDK ≥ 8 vorhanden ist.

Wie Tagir Valeev in diese Antwort bemerkte, ist dies tatsächlich ein Fehler in Vorabversionen von JDK 8 und wurde seitdem behoben, so dass sowohl die if-else Problemumgehung und der erläuternde Kommentar sind jetzt veraltet.

Zusätzlicher Hinweis: Man könnte zu dem Schluss kommen, dass dieser Compiler-Fehler vor Java 8. Allerdings das Byte vorhanden war Der von OpenJDK 7 für das Ternäre generierte Code ist derselbe wie der von OpenJDK 8 generierte Byte-Code. Tatsächlich wird der Typ des Ausdrucks zur Laufzeit nicht erwähnt, der Code besteht lediglich aus Test, Verzweigung, Laden und Rückgabe ohne zusätzliche Überprüfungen Seien Sie also versichert, dass dies (nicht mehr) ein Problem ist und in der Tat ein vorübergehendes Problem bei der Entwicklung von Java 8) gewesen zu sein scheint.

102
dhke

Dies wurde in ziemlich altes 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 Entwicklungsphase, sodass solche Kompatibilitätsprobleme auftreten können. 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. Der ternäre Operator in diesem Code funktioniert nun wie erwartet einwandfrei (es ist kein Verweis auf die Klasse Executable in der kompilierten .class-Datei vorhanden).

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

30
Tagir Valeev

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

Eine Anweisung kann dem Aufrufer auf einigen Steuerpfaden Dinge wie return antun. Ein Ausdruck kann in einer Zuweisung verwendet werden:

int n = condition ? 3 : 2;

Die beiden Ausdrücke im Ternär nach der Bedingung müssen also auf denselben Typ übertragbar sein. Dies kann einige seltsame Effekte in Java insbesondere bei Auto-Boxing und automatischem Referenzcasting verursachen - auf diese bezieht sich der Kommentar in Ihrem veröffentlichten Code. Das Erzwingen der Ausdrücke in Ihrem Fall wäre zu einer Java.lang.reflect.Executable type (da dies der spezialisierteste Typ ist) und das gibt es in älteren Java-Versionen nicht.

Stilistisch sollten Sie einen ifelse -Block verwenden, wenn der Code aussagekräftig ist, und einen ternären, wenn er aussagekräftig ist.

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

7
Bathsheba

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 hätte geschrieben werden können.

6
user207421