Ternärer Java-Operator vs. if/else in

Lesezeit: 1 Minute

Benutzer-Avatar
jddxf

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

HINWEIS: kein ternärer Ausdruck, um die JDK <8-Kompatibilität beizubehalten, selbst wenn der JDK 8-Compiler verwendet wird (möglicherweise auswählen java.lang.reflect.Executable als allgemeiner Typ, wobei diese neue Basisklasse auf älteren JDKs nicht verfügbar ist)

Was ist der Unterschied zwischen der Verwendung von ternären Ausdrücken und der Verwendung von if...else... in diesem Zusammenhang konstruieren?

Benutzer-Avatar
dhke

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

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

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

In Java 7 ist dies java.lang.reflect.Memberdie Java 8-Klassenbibliothek führt jedoch einen neuen Typ ein java.lang.reflect.Executable das spezialisierter ist als das Generikum Member. Daher ist bei einer Java 8-Klassenbibliothek der Ergebnistyp des ternären Ausdrucks Executable statt Member.

Einige (Vorab-)Versionen des Java 8-Compilers scheinen einen expliziten Verweis auf erzeugt zu haben Executable innerhalb des generierten Codes beim Kompilieren des ternären Operators. Dies würde eine Klassenladung auslösen und damit wiederum eine ClassNotFoundException zur Laufzeit bei Ausführung mit einer Klassenbibliothek < JDK 8, weil Executable existiert nur für JDK ≥ 8.

Wie von Tagir Valeev in dieser Antwort erwähnt, handelt es sich tatsächlich um einen Fehler in Vorabversionen von JDK 8, der inzwischen behoben wurde, also sowohl die if-else Workaround und der erläuternde Kommentar sind jetzt obsolet.

Zusätzliche Anmerkung: Man könnte zu dem Schluss kommen, dass dieser Compiler-Bug schon vor Java 8 vorhanden war. Allerdings ist der von OpenJDK 7 generierte Bytecode für die Ternary derselbe wie der von OpenJDK 8 generierte Bytecode. Tatsächlich geht der Typ des Ausdrucks komplett Zur Laufzeit nicht erwähnt, besteht der Code wirklich nur aus Testen, Verzweigen, Laden und 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 gewesen zu sein scheint.

  • Wie kann dann Code, der mit JDK 1.8 kompiliert wurde, auf JDK 1.7 ausgeführt werden? Ich weiß, dass Code, der mit einer niedrigeren JDK-Version kompiliert wurde, problemlos auf einer höheren JDK-Version ausgeführt werden kann. Umgekehrt?

    – jddxf

    9. September 2015 um 10:45 Uhr


  • @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. Probleme treten jedoch zwangsläufig auf, wenn eine solche Verwendung wie in diesem Fall implizit erfolgt.

    – dhke

    9. September 2015 um 11:00 Uhr

  • @jddxf, verwenden Sie die Javac-Optionen -source/-target

    – Tagir Walejew

    9. September 2015 um 11:00 Uhr

  • Vielen Dank an alle, insbesondere dhke und Tagir Valeev, die eine gründliche Erklärung gegeben haben

    – jddxf

    10. September 2015 um 1:21 Uhr

Dies wurde eingeführt in ziemlich altes Commitment am 3. Mai 2013, fast ein Jahr vor der offiziellen Veröffentlichung von JDK-8. Der Compiler wurde damals stark weiterentwickelt, so dass solche Kompatibilitätsprobleme auftreten konnten. Ich schätze, das Spring-Team hat gerade den JDK-8-Build getestet und versucht, Probleme zu beheben, obwohl es sich eigentlich 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 (kein Verweis auf Executable Klasse in kompilierter .class-Datei ist vorhanden).

Derzeit treten ähnliche Dinge in JDK-9 auf: Einige Codes, die in JDK-8 gut kompiliert werden können, schlagen mit JDK-9 javac fehl. Ich denke, die meisten dieser Probleme werden bis zur Veröffentlichung behoben sein.

  • +1. War das also ein Fehler im frühen Compiler? War das Verhalten, wo es sich bezog Executable, unter Verstoß 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?

    – ruach

    10. September 2015 um 8:12 Uhr

  • @ruakh, ich denke, es war der Fehler. In Bytecode (entweder in Java-8 oder früher) ist es völlig unnötig, explizit darauf umzuwandeln Executable tippe dazwischen. In Java-8 hat sich das Konzept der Ausdruckstypinferenz drastisch geändert und dieser Teil wurde komplett neu geschrieben, daher ist es nicht so überraschend, dass frühe Implementierungen Fehler hatten.

    – Tagir Walejew

    10. September 2015 um 9:00 Uhr

Benutzer-Avatar
Bathseba

Der Hauptunterschied besteht darin, dass ein if else Block ist ein Aussage während die ternäre (häufiger bekannt als die bedingt Operator in Java) ist ein Ausdruck.

EIN Aussage kann sowas machen return an den Anrufer auf einigen der Steuerpfade. 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 gezwungen werden. Dies kann in Java zu einigen seltsamen Effekten führen, insbesondere beim automatischen Boxen und automatischen Referenzcasting – darauf bezieht sich der Kommentar in Ihrem geposteten Code. Die Nötigung der Ausdrücke wäre in Ihrem Fall zu a java.lang.reflect.Executable Typ (wie das ist die spezialisierteste Art) und das ist in älteren Java-Versionen nicht vorhanden.

Stilistisch sollten Sie ein verwenden if else block, wenn der Code anweisungsartig ist, und ein ternäres, wenn er ausdrucksartig ist.

Natürlich kann man eine machen if else block verhalten sich wie ein Ausdruck, wenn Sie eine Lambda-Funktion verwenden.

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.

1312030cookie-checkTernärer Java-Operator vs. if/else in

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy