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?
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.
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.
Der Hauptunterschied besteht darin, dass ein if
else
-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 if
else
-Block verwenden, wenn der Code aussagekräftig ist, und einen ternären, wenn er aussagekräftig ist.
Natürlich können Sie einen if
else
-Block wie einen Ausdruck verhalten lassen, 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.