Bisher dachte ich, dass effektiv final und final mehr oder weniger gleichwertig sind und dass die JLS sie ähnlich behandeln würde, wenn nicht identisch im tatsächlichen Verhalten. Dann fand ich dieses erfundene Szenario:
final int a = 97;
System.out.println(true ? a : 'c'); // outputs a
// versus
int a = 97;
System.out.println(true ? a : 'c'); // outputs 97
Anscheinend macht die JLS hier einen wichtigen Unterschied zwischen den beiden und ich bin mir nicht sicher warum.
Ich lese andere Themen wie
- Unterschied zwischen endgültig und effektiv endgültig
- Effektiv endgültige Variable gegen endgültige Variable
- Was bedeutet es, wenn eine Variable „effektiv endgültig“ ist?
aber sie gehen nicht auf solche Details ein. Auf einer breiteren Ebene scheinen sie schließlich ziemlich gleichwertig zu sein. Aber wenn sie tiefer graben, unterscheiden sie sich anscheinend.
Was verursacht dieses Verhalten? Kann jemand einige JLS-Definitionen bereitstellen, die dies erklären?
Bearbeiten: Ich habe ein anderes verwandtes Szenario gefunden:
final String a = "a";
System.out.println(a + "b" == "ab"); // outputs true
// versus
String a = "a";
System.out.println(a + "b" == "ab"); // outputs false
Daher verhält sich das String-Interning auch hier anders (ich möchte dieses Snippet nicht in echtem Code verwenden, nur neugierig auf das unterschiedliche Verhalten).
quelle
byte
,short
oderchar
, und der andere Operand ein konstanter Ausdruck von Typ,int
dessen Wert in Typ T darstellbar ist , dann ist der Typ des bedingten Ausdrucks T. " --- Ich habe sogar ein Java 1.0-Dokument in Berkeley gefunden. Gleicher Text . --- Ja, das war schon immer so.Antworten:
Zunächst sprechen wir nur über lokale Variablen . Tatsächlich gilt final nicht für Felder. Dies ist wichtig, da die Semantik für
final
Felder sehr unterschiedlich ist und umfangreichen Compiler-Optimierungen und Versprechungen für das Speichermodell unterliegt (siehe 17.5.1 USD zur Semantik der endgültigen Felder).Auf Oberflächenebene
final
undeffectively final
für lokale Variablen sind in der Tat identisch. Das JLS unterscheidet jedoch klar zwischen den beiden, was in solchen besonderen Situationen tatsächlich eine Vielzahl von Auswirkungen hat.Prämisse
Aus JLS§4.12.4 über
final
Variablen:Da
int
es primitiv ist, ist die Variablea
eine solche konstante Variable .Weiter aus dem gleichen Kapitel über
effectively final
:So aus dem Weg , dies formuliert wird, ist es klar , dass in dem anderen Beispiel,
a
ist nicht eine konstante Größe betrachtet, wie es ist nicht endgültig , sondern nur effektiv endgültig.Verhalten
Nachdem wir nun die Unterscheidung getroffen haben, schauen wir nach, was los ist und warum die Ausgabe anders ist.
Sie verwenden hier den bedingten Operator
? :
, daher müssen wir seine Definition überprüfen. Aus JLS§15.25 :In diesem Fall handelt es sich um numerische bedingte Ausdrücke aus JLS§15.25.2 :
Und das ist der Teil, in dem die beiden Fälle unterschiedlich klassifiziert werden.
effektiv endgültig
Die Version,
effectively final
die dieser Regel entspricht:Welches ist das gleiche Verhalten, als ob Sie tun würden
5 + 'd'
, dhint + char
was zu führtint
. Siehe JLS§5.6So wird alles so gefördert,
int
wiea
esint
schon ist. Das erklärt die Ausgabe von97
.Finale
Die Version mit der
final
Variablen entspricht dieser Regel:Die letzte Variable
a
ist vom Typint
und ein konstanter Ausdruck (weil es so istfinal
). Es ist darstellbar alschar
, daher ist das Ergebnis vom Typchar
. Damit ist die Ausgabe abgeschlossena
.String-Beispiel
Das Beispiel mit der Zeichenfolgengleichheit basiert auf demselben Kernunterschied,
final
Variablen werden als konstanter Ausdruck / Variable behandelt undeffectively final
nicht.In Java basiert die String-Internierung daher auf konstanten Ausdrücken
"a" + "b" + "c" == "abc"
ist
true
auch (verwenden Sie dieses Konstrukt nicht in echtem Code).Siehe JLS§3.10.5 :
Leicht zu übersehen, da es sich hauptsächlich um Literale handelt, aber es gilt auch für konstante Ausdrücke.
quelle
... ? a : 'c'
sich gleich verhalten, unabhängig davon , oba
es sich um eine Variable oder eine Konstante handelt . An dem Ausdruck ist anscheinend nichts auszusetzen. --- Im Gegensatz dazua + "b" == "ab"
ist dies ein schlechter Ausdruck , da Zeichenfolgen mitequals()
( Wie vergleiche ich Zeichenfolgen in Java? ) Verglichen werden müssen . Die Tatsache, dass es "versehentlich" funktioniert, wenna
es sich um eine Konstante handelt , ist nur eine Eigenart der Internierung von String-Literalen."a" + "b" + "c" == "abc"
muss sichtrue
in einer gültigen Java-Implementierung befinden.a + "b" == "ab"
immer noch ein falscher Ausdruck . Selbst wenn Sie wissen, dass diesa
eine Konstante ist , ist sie zu fehleranfällig, um nicht aufzurufenequals()
. Oder vielleicht ist zerbrechlich ein besseres Wort, dh es ist zu wahrscheinlich, dass es auseinander fällt, wenn der Code in Zukunft beibehalten wird.(final) String str = "a"; Stream.of(null, null). <Runnable>map( x -> () -> System.out.println(str)) .reduce((a,b) -> () -> System.out.println(a == b)) .ifPresent(Runnable::run);
ändert das Ergebnis, wennstr
(nicht)final
.Ein weiterer Aspekt ist, dass die Variable, wenn sie im Hauptteil der Methode als final deklariert wird, ein anderes Verhalten aufweist als eine als Parameter übergebene endgültige Variable.
public void testFinalParameters(final String a, final String b) { System.out.println(a + b == "ab"); } ... testFinalParameters("a", "b"); // Prints false
während
public void testFinalVariable() { final String a = "a"; final String b = "b"; System.out.println(a + b == "ab"); // Prints true } ... testFinalVariable();
es geschieht , weil der Compiler weiß , dass die Verwendung
final String a = "a"
dera
Variable wird immer das hat ,"a"
so dass Werta
und"a"
kann problemlos ausgetauscht werden. Wenn der Compilera
nicht definiertfinal
oder definiert istfinal
, sein Wert jedoch zur Laufzeit zugewiesen wird (wie im obigen Beispiel, in dem final dera
Parameter ist), weiß er vor seiner Verwendung nichts. Die Verkettung erfolgt also zur Laufzeit und es wird eine neue Zeichenfolge generiert, die den internen Pool nicht verwendet.Grundsätzlich lautet das Verhalten: Wenn der Compiler weiß, dass eine Variable eine Konstante ist, kann er sie genauso verwenden wie die Konstante.
Wenn die Variable nicht endgültig definiert ist (oder endgültig ist, ihr Wert jedoch zur Laufzeit definiert ist), gibt es für den Compiler keinen Grund, sie als Konstante zu behandeln, auch wenn ihr Wert einer Konstanten entspricht und ihr Wert niemals geändert wird.
quelle
finel
Schlüsselwort, das auf einen Parameter angewendet wird, hat eine andere Semantik alsfinal
auf eine lokale Variable angewendet usw.final String a; a = "a";
das gleiche Verhalten tun und bekommen