tl; dr
Pour les champs , int b = b + 1
est illégal car il b
s'agit d'une référence directe illégale à b
. Vous pouvez réellement résoudre ce problème en écrivant int b = this.b + 1
, qui compile sans se plaindre.
Pour les variables locales , int d = d + 1
est illégal car d
n'est pas initialisé avant utilisation. Ce n'est pas le cas pour les champs, qui sont toujours initialisés par défaut.
Vous pouvez voir la différence en essayant de compiler
int x = (x = 1) + x;
comme déclaration de champ et comme déclaration de variable locale. Le premier échouera, mais le second réussira, en raison de la différence de sémantique.
introduction
Tout d'abord, les règles des initialiseurs de champ et de variable locale sont très différentes. Cette réponse abordera donc les règles en deux parties.
Nous utiliserons ce programme de test partout:
public class test {
int a = a = 1;
int b = b + 1;
public static void Main(String[] args) {
int c = c = 1;
int d = d + 1;
}
}
La déclaration de b
n'est pas valide et échoue avec une illegal forward reference
erreur.
La déclaration de d
n'est pas valide et échoue avec une variable d might not have been initialized
erreur.
Le fait que ces erreurs soient différentes devrait indiquer que les raisons des erreurs sont également différentes.
Des champs
Les initialiseurs de champs en Java sont régis par JLS §8.3.2 , Initialisation des champs.
La portée d'un champ est définie dans JLS §6.3 , Portée d'une déclaration.
Les règles pertinentes sont:
- La portée d'une déclaration d'un membre
m
déclaré dans ou héritée par une classe de type C (§8.1.6) est le corps entier de C, y compris toutes les déclarations de type imbriquées.
- Les expressions d'initialisation pour les variables d'instance peuvent utiliser le nom simple de toute variable statique déclarée ou héritée par la classe, même celle dont la déclaration se produit textuellement plus tard.
- L'utilisation de variables d'instance dont les déclarations apparaissent textuellement après l'utilisation est parfois restreinte, même si ces variables d'instance sont dans la portée. Voir §8.3.2.3 pour les règles précises régissant la référence directe aux variables d'instance.
§8.3.2.3 dit:
La déclaration d'un membre doit apparaître textuellement avant d'être utilisée uniquement si le membre est un champ d'instance (respectivement statique) d'une classe ou d'une interface C et que toutes les conditions suivantes sont remplies:
- L'utilisation se produit dans un initialiseur de variable d'instance (respectivement statique) de C ou dans un initialiseur d'instance (respectivement statique) de C.
- L'utilisation n'est pas sur le côté gauche d'une affectation.
- L'utilisation se fait via un simple nom.
- C est la classe ou l'interface la plus interne englobant l'utilisation.
Vous pouvez en fait faire référence à des champs avant qu'ils n'aient été déclarés, sauf dans certains cas. Ces restrictions visent à empêcher le code comme
int j = i;
int i = j;
de la compilation. La spécification Java indique que "les restrictions ci-dessus sont conçues pour intercepter, au moment de la compilation, les initialisations circulaires ou autrement mal formées".
À quoi se résument ces règles?
En bref, les règles disent essentiellement que vous devez déclarer un champ avant une référence à ce champ si (a) la référence est dans un initialiseur, (b) la référence n'est pas affectée à, (c) la référence est un nom simple (pas de qualificatif comme this.
) et (d) il n'est pas accessible depuis une classe interne. Ainsi, une référence directe qui satisfait les quatre conditions est illégale, mais une référence directe qui échoue sur au moins une condition est OK.
int a = a = 1;
compile parce qu'il viole (b): la référence a
est assignée, il est donc légal de s'y référer a
avant a
la déclaration complète de.
int b = this.b + 1
compile également car il viole (c): la référence this.b
n'est pas un simple nom (il est qualifié par this.
). Cette construction étrange est toujours parfaitement bien définie, car elle this.b
a la valeur zéro.
Donc, fondamentalement, les restrictions sur les références de champ dans les initialiseurs empêchent int a = a + 1
une compilation réussie.
Notez que la déclaration sur le terrain int b = (b = 1) + b
va échouer à compiler, parce que la finale b
est encore une référence avant illégale.
Variables locales
Les déclarations de variables locales sont régies par JLS §14.4 , Instructions de déclaration de variables locales.
La portée d'une variable locale est définie dans JLS §6.3 , Portée d'une déclaration:
- La portée d'une déclaration de variable locale dans un bloc (§14.4) est le reste du bloc dans lequel la déclaration apparaît, en commençant par son propre initialiseur et en incluant tout autre déclarateur à droite dans l'instruction de déclaration de variable locale.
Notez que les initialiseurs sont dans la portée de la variable déclarée. Alors pourquoi ne int d = d + 1;
compile pas ?
La raison est due à la règle de Java sur l'affectation définie ( JLS §16 ). L'affectation définie indique essentiellement que chaque accès à une variable locale doit avoir une affectation précédente à cette variable, et le compilateur Java vérifie les boucles et les branches pour s'assurer que l'affectation se produit toujours avant toute utilisation (c'est pourquoi une affectation définie a une section de spécification entière dédiée à lui). La règle de base est:
- Pour chaque accès à une variable locale ou à un champ final vide
x
, x
doit être définitivement attribué avant l'accès, ou une erreur de compilation se produit.
Dans int d = d + 1;
, l'accès à d
est résolu à la variable locale bien, mais comme d
n'a pas été affecté avant l' d
accès, le compilateur émet une erreur. Dans int c = c = 1
, c = 1
se produit en premier, qui affecte c
, puis c
est initialisé au résultat de cette affectation (qui est 1).
Notez qu'en raison de règles d'affectation définies, la déclaration de variable locale int d = (d = 1) + d;
sera compilée avec succès ( contrairement à la déclaration de champ int b = (b = 1) + b
), car elle d
est définitivement affectée au moment où la finale d
est atteinte.
static
dans la variable class-scope, comme dansstatic int x = x + 1;
, obtiendrez-vous la même erreur? Parce qu'en C #, cela fait une différence si c'est statique ou non statique.