Un cas très intéressant de trouver. Pour le comprendre, nous avons besoin de creuser dans le Langage Java Specification (JLS).
La raison en est qu' final
ne permet qu'une seule affectation. La valeur par défaut, cependant, n'est pas d'affectation. En fait, chaque variable (variable de classe, variable d'instance, de composants de la matrice) points à sa valeur par défaut depuis le début, avant les affectations. La première tâche puis change de la référence.
Variables de classe et de la valeur par défaut
Jetez un oeil à l'exemple suivant:
private static Object x;
public static void main(String[] args) {
System.out.println(x); // Prints 'null'
}
Nous n'avons pas explicitement attribuer une valeur à l' x
, mais il points de null
, c'est la valeur par défaut. Comparez §4.12.5:
Valeurs initiales des Variables
Chaque variable de classe, variable d'instance, ou de composants de la matrice est initialisé avec une valeur par défaut lorsqu'il est créé (§15.9, §15.10.2)
Notez que cela ne tient que pour ce type de variables, comme dans notre exemple. Il n'est pas valable pour les variables locales, voir l'exemple suivant:
public static void main(String[] args) {
Object x;
System.out.println(x);
// Compile-time error:
// variable x might not have been initialized
}
De la même JLS paragraphe:
Une variable locale (§14.4, §14.14) doit être donné explicitement une valeur avant de l'utiliser, soit par l'initialisation (§14.4) ou de cession (§15.26), d'une manière qui peut être vérifié en utilisant les règles pour l'affectation définitive (§16 (Nette de Cession)).
Les variables Final
Maintenant, nous prenons un coup d'oeil à l' final
, à partir du§4.12.4:
final Variables
Une variable peut être déclarée final. Une finale de la variable ne peut être attribué à la fois. C'est une erreur de compilation si une finale est attribué à la variable, sauf si elle est certainement non affecté immédiatement avant la cession (§16 (Nette de Cession)).
Explication
Maintenant revenir à votre exemple, légèrement modifiée:
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;
}
C'sorties
Before: 0
After: 1
Rappel de ce que nous avons appris. À l'intérieur de la méthode de assign
la variable X
a pas attribué une valeur encore. Par conséquent, il des points à sa valeur par défaut, car il est une variable de classe et selon la JLS ces variables toujours immédiatement le signaler à leurs valeurs par défaut (contrairement aux variables locales). Après l' assign
méthode de la variable X
est affectée de la valeur 1
, et en raison de final
on ne peut pas changer plus. Donc la suite ne serait pas travailler en raison d' final
:
private static long assign() {
// Assign X
X = 1;
// Second assign after method will crash
return X + 1;
}
Exemple dans le JLS
Grâce à @Andrew, j'ai trouvé un JLS paragraphe qui couvre exactement ce scénario, il démontre également qu'il.
Mais d'abord, nous allons jeter un oeil à
private static final long X = X + 1;
// Compile-time error:
// self-reference in initializer
Pourquoi ce n'est pas permis, alors que l'accès à partir de la méthode est? Regardez §8.3.3 qui parle lorsque les accès aux champs sont limités si le champ n'a pas encore été initialisé.
Il répertorie certaines des règles applicables pour les variables de classe:
Pour une référence par le simple nom d'une variable de classe f
déclarée dans la classe ou de l'interface C
, c'est une erreur de compilation si:
La référence apparaît dans une variable de classe de l'initialiseur de C
ou dans un initialiseur statique d' C
(§8.7); et
La référence apparaît dans l'initialiseur de f
's propre déclaration ou à un point vers la gauche de l' f
's de demande de déclaration; et
La référence n'est pas sur le côté de main gauche d'une expression d'affectation (§15.26); et
Le plus profond de la classe ou de l'interface entourant la référence est - C
.
C'est simple, l' X = X + 1
est pris par ces règles, la méthode d'accès pas. Ils ont même liste de ce scénario et de donner un exemple:
Accès par les méthodes ne sont pas contrôlés de cette façon, donc:
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);
}
}
produit de la sortie:
0
parce que la variable d'initialiseur pour i
utilise la méthode de la classe peek pour accéder à la valeur de la variable j
avant j
a été initialisé par la variable d'initialiseur, à quel point il a encore sa valeur par défaut (§4.12.5).