Ein sehr interessanter Fund. Um es zu verstehen, müssen wir uns mit der Java Language Specification ( JLS ) befassen .
Der Grund ist, dass finalnur eine Zuordnung erlaubt . Der Standardwert ist jedoch keine Zuordnung . Tatsächlich zeigt jede solche Variable (Klassenvariable, Instanzvariable, Array-Komponente) vor den Zuweisungen von Anfang an auf ihren Standardwert . Die erste Zuordnung ändert dann die Referenz.
Klassenvariablen und Standardwert
Schauen Sie sich das folgende Beispiel an:
private static Object x;
public static void main(String[] args) {
System.out.println(x); // Prints 'null'
}
Wir haben dem Standardwert keinen expliziten Wert zugewiesen x, obwohl er darauf verweist null. Vergleichen Sie das mit §4.12.5 :
Anfangswerte von Variablen
Jede Klassenvariablen , Instanzvariablen oder Arraykomponenten mit einem initialisierten Standardwert , wenn er erstellt ( §15.9 , §15.10.2 )
Beachten Sie, dass dies nur für diese Art von Variablen gilt, wie in unserem Beispiel. Es gilt nicht für lokale Variablen, siehe folgendes Beispiel:
public static void main(String[] args) {
Object x;
System.out.println(x);
// Compile-time error:
// variable x might not have been initialized
}
Aus demselben JLS-Absatz:
Eine lokale Variable ( §14.4 , §14.14 ) müssen explizit einen Wert gegeben , bevor es verwendet wird, entweder durch Initialisierung ( §14.4 ) oder Zuweisung ( §15.26 ), in einer Weise , die mit den Regeln für die eindeutige Zuordnung überprüft werden kann ( § 16 (definitive Zuordnung) ).
Letzte Variablen
Nun schauen wir uns finalab §4.12.4 an :
endgültige Variablen
Eine Variable kann als endgültig deklariert werden . Eine endgültige Variable darf nur einmal zugewiesen werden . Es ist ein Fehler bei der Kompilierung, wenn eine endgültige Variable zugewiesen wird, es sei denn, sie wird unmittelbar vor der Zuweisung definitiv nicht zugewiesen ( §16 (Definitive Zuweisung) ).
Erläuterung
Kommen wir nun zu Ihrem Beispiel zurück, das leicht modifiziert wurde:
public static void main(String[] args) {
System.out.println("After: " + X);
}
private static final long X = assign();
private static long assign() {
// Access the value before first assignment
System.out.println("Before: " + X);
return X + 1;
}
Es gibt aus
Before: 0
After: 1
Erinnern Sie sich an das, was wir gelernt haben. Innerhalb der Methode wurde assignder Variablen Xnoch kein Wert zugewiesen . Daher zeigt es auf seinen Standardwert, da es sich um eine Klassenvariable handelt, und laut JLS zeigen diese Variablen immer sofort auf ihre Standardwerte (im Gegensatz zu lokalen Variablen). Nach der assignMethode wird der Variablen Xder Wert zugewiesen 1und finalwir können ihn deshalb nicht mehr ändern. Folgendes würde also aufgrund von nicht funktionieren final:
private static long assign() {
// Assign X
X = 1;
// Second assign after method will crash
return X + 1;
}
Beispiel in der JLS
Dank @Andrew habe ich einen JLS-Absatz gefunden, der genau dieses Szenario abdeckt und es auch demonstriert.
Aber zuerst schauen wir uns das an
private static final long X = X + 1;
// Compile-time error:
// self-reference in initializer
Warum ist dies nicht erlaubt, während der Zugriff von der Methode ist? Schauen Sie sich §8.3.3 an, in dem erläutert wird , wann der Zugriff auf Felder eingeschränkt ist, wenn das Feld noch nicht initialisiert wurde.
Es werden einige Regeln aufgelistet, die für Klassenvariablen relevant sind:
Bei einem Verweis mit einfachem Namen auf eine fin einer Klasse oder Schnittstelle deklarierte Klassenvariable Chandelt es sich um einen Fehler zur Kompilierungszeit, wenn :
Die Referenz erscheint entweder in einem Klassenvariableninitialisierer von Coder in einem statischen Initialisierer von C( §8.7 ); und
Die Referenz erscheint entweder im Initialisierer des feigenen Deklarators oder an einer Stelle links vom fDeklarator. und
Die Referenz befindet sich nicht auf der linken Seite eines Zuweisungsausdrucks ( §15.26 ); und
Die innerste Klasse oder Schnittstelle, die die Referenz einschließt, ist C.
Es ist einfach, das X = X + 1wird von diesen Regeln abgefangen, der Methodenzugriff nicht. Sie listen sogar dieses Szenario auf und geben ein Beispiel:
Zugriffe nach Methoden werden auf diese Weise nicht überprüft.
class Z {
static int peek() { return j; }
static int i = peek();
static int j = 1;
}
class Test {
public static void main(String[] args) {
System.out.println(Z.i);
}
}
erzeugt die Ausgabe:
0
da die Variable Initialisierer für iVerwendungen der Klassenmethode peek der Wert der Variable zuzugreifen , jbevor jdurch seine variable Initialisierer initialisiert wurde, an welcher Stelle es noch den Standardwert hat ( §4.12.5 ).
XElements ist wie die Bezugnahme auf ein Unterklassenmitglied, bevor der Superklassenkonstruktor fertig ist. Das ist Ihr Problem und nicht die Definition vonfinal.