La description complète se trouve dans la section 8.3.3 de la spécification du langage Java : "Références Avant l'Initialisation des Champs"
Une référence avant (référence à une variable qui n'est pas encore déclarée à ce moment-là) n'est considérée comme une erreur que si toutes les conditions suivantes sont remplies :
-
La déclaration d'une variable d'instance dans une classe ou interface C apparaît textuellement après une utilisation de la variable d'instance ;
-
L'utilisation est un simple name soit dans un initialisateur de variable d'instance de C soit dans un initialiseur d'instance de C ;
-
L'utilisation ne se trouve pas du côté gauche d'une affectation ;
-
C est la classe ou interface la plus imbriquée contenant l'utilisation.
Voir le texte en gras : "l'utilisation est un simple name". Un simple name est un nom de variable sans qualification supplémentaire. Dans votre code, b
est un simple name, mais this.b
ne l'est pas.
Mais pourquoi ?
La raison est, comme le texte en italique dans l'exemple de la JLS l'indique :
"Les restrictions ci-dessus sont conçues pour détecter, au moment de la compilation, des initialisations circulaires ou autrement mal formées. "
En d'autres termes, elles autorisent this.b
car elles pensent qu'une référence qualifiée rend plus probable que vous avez bien réfléchi à ce que vous faites, mais simplement utiliser b
signifie probablement que vous avez fait une erreur.
C'est la logique des concepteurs du langage Java. À ma connaissance, il n'a jamais été étudié si cela était vrai en pratique.
Ordre d'initialisation
Pour approfondir ce qui précède, en référence au commentaire de Dukeling sur la question, l'utilisation d'une référence qualifiée this.b
ne vous donnera probablement pas les résultats que vous souhaitez.
Je restreins cette discussion aux variables d'instance car l'OP s'y est seulement référé. L'ordre dans lequel les variables d'instance sont assignées est décrit dans JLS 12.5 Création de Nouvelles Instances de Classe. Vous devez prendre en compte que les constructeurs de superclasses sont invoqués en premier, et que le code d'initialisation (affectations et blocs d'initialisation) est exécuté dans l'ordre textuel.
Ainsi, étant donné
int a = this.b;
int b = 2;
vous obtiendrez a
étant égal à zéro (la valeur de b
au moment où l'initialiseur de a
a été exécuté) et b
étant égal à 2.
Des résultats encore plus étranges peuvent être obtenus si le constructeur de la superclasse invoque une méthode qui est redéfinie dans la sous-classe et que cette méthode affecte une valeur à b
.
Donc, en général, il est une bonne idée de faire confiance au compilateur et de soit réorganiser vos champs soit de corriger le problème sous-jacent en cas d'initialisations circulaires.
Si vous avez besoin d'utiliser this.b
pour contourner l'erreur du compilateur, alors vous êtes probablement en train d'écrire un code qui sera très difficile à maintenir pour la personne qui viendra après vous.