Une trouvaille très intéressante. Pour le comprendre, nous devons creuser dans la spécification du langage Java ( JLS ).
La raison en est que final
n'autorise qu'une seule affectation . Cependant, la valeur par défaut est aucune affectation . En fait, chacune de ces variables ( variable de classe, variable d'instance, composant de tableau) pointe vers sa valeur par défaut depuis le début, avant les affectations . La première affectation modifie ensuite la référence.
Variables de classe et valeur par défaut
Jetez un œil à l'exemple suivant:
private static Object x;
public static void main(String[] args) {
System.out.println(x); // Prints 'null'
}
Nous n'avons pas explicitement attribué de valeur à x
, bien qu'elle pointe vers null
, c'est la valeur par défaut. Comparez cela au §4.12.5 :
Valeurs initiales des variables
Chaque variable de classe, variable d' instance ou composant de tableau est initialisé avec une valeur par défaut lors de sa création ( §15.9 , §15.10.2 )
Notez que cela ne vaut que pour ce type de variables, comme dans notre exemple. Il ne tient pas 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
}
Du même paragraphe JLS:
Une variable locale ( §14.4 , §14.14 ) doit recevoir une valeur explicite avant d'être utilisée, soit par initialisation ( §14.4 ), soit par affectation ( §15.26 ), d'une manière qui peut être vérifiée à l'aide des règles d'assignation définie ( § 16 (Affectation définitive) ).
Variables finales
Maintenant, nous regardons final
, à partir du §4.12.4 :
Variables finales
Une variable peut être déclarée définitive . Une dernière variable ne peut être affectée qu'une seule fois . Il s'agit d'une erreur de compilation si une variable finale est affectée à moins qu'elle ne soit définitivement non affectée immédiatement avant l'affectation ( §16 (Affectation définitive) ).
Explication
Revenons maintenant à votre exemple, légèrement modifié:
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;
}
Il sort
Before: 0
After: 1
Rappelez-vous ce que nous avons appris. À l'intérieur de la méthode, aucune valeur n'a encore été attribuée àassign
la variable . Par conséquent, il pointe vers sa valeur par défaut puisqu'il s'agit d'une variable de classe et, selon le JLS, ces variables pointent toujours immédiatement vers leurs valeurs par défaut (contrairement aux variables locales). Après la méthode, la valeur est attribuée à la variable et à cause de cela, nous ne pouvons plus la modifier. Ainsi, les éléments suivants ne fonctionneraient pas en raison de :X
assign
X
1
final
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 paragraphe JLS qui couvre exactement ce scénario, il le démontre également.
Mais regardons d'abord
private static final long X = X + 1;
// Compile-time error:
// self-reference in initializer
Pourquoi n'est-ce pas autorisé, alors que l'accès à partir de la méthode l'est? Jetez un œil au §8.3.3 qui parle du moment où les accès aux champs sont restreints si le champ n'a pas encore été initialisé.
Il répertorie quelques règles pertinentes pour les variables de classe:
Pour une référence par simple nom à une variable de classe f
déclarée dans la classe ou l'interface C
, il s'agit d'une erreur de compilation si :
La référence apparaît soit dans un initialiseur de variable de classe de, C
soit dans un initialiseur statique de C
( §8.7 ); et
La référence apparaît soit dans l'initialiseur du f
propre déclarateur de 's ou à un point à gauche du f
déclarateur de ' s; et
La référence n'est pas sur le côté gauche d'une expression d'affectation ( §15.26 ); et
La classe ou l'interface la plus interne englobant la référence est C
.
C'est simple, le X = X + 1
est pris par ces règles, l'accès à la méthode non. Ils énumèrent même ce scénario et donnent un exemple:
Les accès par méthodes ne sont pas vérifiés de cette manière, 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 la sortie:
0
car l'initialiseur de variable pour i
utilise la méthode de classe peek pour accéder à la valeur de la variable j
avant qu'elle j
ait été initialisée par son initialiseur de variable, à quel point elle a toujours sa valeur par défaut ( §4.12.5 ).
X
membre revient à faire référence à un membre de sous-classe avant que le constructeur de la super classe n'ait fini, c'est votre problème et non la définition definal
.