Ein sehr interessanter Fund. Um es zu verstehen, müssen wir uns mit der Java Language Specification ( JLS ) befassen .
Der Grund ist, dass final
nur 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 final
ab §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 assign
der Variablen X
noch 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 assign
Methode wird der Variablen X
der Wert zugewiesen 1
und final
wir 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 f
in einer Klasse oder Schnittstelle deklarierte Klassenvariable C
handelt es sich um einen Fehler zur Kompilierungszeit, wenn :
Die Referenz erscheint entweder in einem Klassenvariableninitialisierer von C
oder in einem statischen Initialisierer von C
( §8.7 ); und
Die Referenz erscheint entweder im Initialisierer des f
eigenen Deklarators oder an einer Stelle links vom f
Deklarator. 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 + 1
wird 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 i
Verwendungen der Klassenmethode peek der Wert der Variable zuzugreifen , j
bevor j
durch seine variable Initialisierer initialisiert wurde, an welcher Stelle es noch den Standardwert hat ( §4.12.5 ).
X
Elements ist wie die Bezugnahme auf ein Unterklassenmitglied, bevor der Superklassenkonstruktor fertig ist. Das ist Ihr Problem und nicht die Definition vonfinal
.