Pourquoi ce code Java comstack-t-il?

Dans la scope de la méthode ou de la classe, la ligne ci-dessous comstack (avec avertissement):

int x = x = 1; 

Dans la scope de classe, où les variables obtiennent leurs valeurs par défaut , l’erreur suivante indique une erreur de référence non définie:

 int x = x + 1; 

N’est-ce pas le premier x = x = 1 devrait se retrouver avec la même erreur de «référence indéfinie»? Ou peut-être que la seconde ligne int x = x + 1 devrait être compilée? Ou il y a quelque chose qui me manque?

tl; dr

Pour les champs , int b = b + 1 est illégal car b est une référence illégale à b . Vous pouvez effectivement résoudre ce problème en écrivant int b = this.b + 1 , qui comstack sans plaintes.

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 comstackr

int x = (x = 1) + x;

sous forme de déclaration de champ et de déclaration de variable locale. Le premier échouera, mais le second réussira, à cause de la différence de sémantique.

introduction

Tout d’abord, les règles pour les initialiseurs de champs et de variables locales sont très différentes. Donc, cette réponse abordera les règles en deux parties.

Nous utiliserons ce programme de test tout au long de:

 public class test { int a = a = 1; int b = b + 1; public static void Main(Ssortingng[] args) { int c = c = 1; int d = d + 1; } } 

La déclaration de b n’est pas valide et échoue avec une erreur de illegal forward reference .
La déclaration de d n’est pas valide et échoue avec une variable d might not have been initialized .

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 le §8.3.2 de JLS , Initialisation des champs.

La scope d’un champ est définie au § 6.1 de la norme JLS , Portée d’une déclaration.

Les règles pertinentes sont les suivantes:

  • La scope d’une déclaration d’un membre m déclaré ou hérité par un type de classe C (§8.1.6) est le corps entier de C, y compris les déclarations de type nestedes.
  • 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.
  • Utilisation de variables d’instance dont les déclarations apparaissent textuellement après utilisation est parfois restreinte, même si ces variables d’instance sont dans la scope. 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 instance (respectivement statique) d’une classe ou d’une interface C et que toutes les conditions suivantes sont remplies:

  • L’utilisation se produit dans une instance (respectivement statique) d’initialiseur de variable de C ou dans une instance (respectivement statique) d’initialiseur de C.
  • L’utilisation n’est pas du côté gauche d’une affectation.
  • L’utilisation se fait via un nom simple.
  • C est la classe ou l’interface la plus interne contenant l’utilisation.

Vous pouvez en fait faire référence aux champs avant qu’ils aient été déclarés, sauf dans certains cas. Ces ressortingctions visent à empêcher le code comme

 int j = i; int i = j; 

de la compilation. La spécification Java indique que “les ressortingctions ci-dessus sont conçues pour attraper, lors de la compilation, des initialisations circulaires ou mal formées”.

À quoi ces règles se résument-elles?

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 se trouve dans un initialiseur, (b) la référence n’est pas affectée à, (c) la référence est un nom simple (aucun qualificatif comme this. ) et (d) il n’est pas accessible depuis une classe interne. Ainsi, une référence en aval qui satisfait aux quatre conditions est illégale, mais une référence en aval qui échoue à au moins une condition est OK.

int a = a = 1; comstack parce qu’il viole (b): la référence a est assignée à, donc il est légal de se référer à a déclaration préalable à a complète.

int b = this.b + 1 comstack aussi parce qu’il viole (c): la référence this.b n’est pas un simple nom (elle est qualifiée avec this. ). Cette construction impaire est toujours parfaitement définie, car this.b a la valeur zéro.

Donc, au fond, les ressortingctions sur les références de champs dans les initialiseurs empêchent la compilation de int a = a + 1 .

Observez que la déclaration de champ int b = (b = 1) + b ne sera pas compilée, car le b final est toujours une référence directe illégale.

Variables locales

Les déclarations de variables locales sont régies par JLS §14.4 , Instructions de déclaration des variables locales.

La scope d’une variable locale est définie dans le § 6.1 de la norme JLS , Champ d’application d’une déclaration:

  • L’étendue d’une déclaration de variable locale dans un bloc (§14.4) est le rest du bloc dans lequel la déclaration apparaît, en commençant par son propre initialiseur et en incluant tous les autres déclarateurs à droite dans la déclaration de la variable locale.

Notez que les initialiseurs sont dans la scope de la variable en cours de déclaration. Alors pourquoi ne pas int d = d + 1; comstackr?

La raison est due à la règle de Java sur l’affectation définitive ( JLS §16 ). L’affectation définitive dit fondamentalement que chaque access à une variable locale doit avoir une affectation précédente à cette variable, et le compilateur Java vérifie les boucles et les twigs pour s’assurer que l’affectation se produit avant toute utilisation (c’est pourquoi à elle). La règle de base est la suivante:

  • Pour chaque access à une variable locale ou à un champ final vide, x , x doit être définitivement atsortingbué avant que l’access ou une erreur de compilation ne se produise.

In int d = d + 1; , l’access à d est résolu dans la variable locale fine, mais comme d n’a pas été assigné avant l’access à d , le compilateur émet une erreur. Dans int c = c = 1 , c = 1 arrive en premier, ce qui assigne c , puis c est initialisé au résultat de cette affectation (qui est 1).

Notez qu’en raison des règles d’atsortingbution définies, la déclaration de la variable locale int d = (d = 1) + d; comstackra avec succès ( contrairement à la déclaration de champ int b = (b = 1) + b ), car d est définitivement atsortingbué au moment où le d final est atteint.

 int x = x = 1; 

est équivalent à

 int x = 1; x = x; //warning here 

alors que dans

 int x = x + 1; 

Nous devons d’abord calculer x+1 mais la valeur de x n’est pas connue, vous obtenez donc une erreur (le compilateur sait que la valeur de x n’est pas connue)

C’est à peu près équivalent à:

 int x; x = 1; x = 1; 

Tout d’abord, int = ; est toujours équivalent à

 int ;  = ; 

Dans ce cas, votre expression est x = 1 , qui est également une déclaration. x = 1 est une déclaration valide, car la variable var a déjà été déclarée. C’est aussi une expression avec la valeur 1, qui est ensuite assignée à nouveau à x .

En java ou dans n’importe quelle langue moderne, l’affectation vient de la droite.

Supposons que si vous avez deux variables x et y,

 int z = x = y = 5; 

Cette instruction est valide et c’est ainsi que le compilateur les divise.

 y = 5; x = y; z = x; // which will be 5 

Mais dans ton cas

 int x = x + 1; 

Le compilateur a donné une exception car il se divise comme ceci.

 x = 1; // oops, it isn't declared because assignment comes from the right. 

int x = x = 1; n’est pas égal à:

 int x; x = 1; x = x; 

javap nous aide à nouveau, ce sont des instructions JVM générées pour ce code:

 0: iconst_1 //load constant to stack 1: dup //duplicate it 2: istore_1 //set x to constant 3: istore_1 //set x to constant 

plus comme:

 int x = 1; x = 1; 

Il n’y a aucune raison de lancer une erreur de référence indéfinie. Il y a maintenant utilisation de la variable avant son initialisation, donc ce code est entièrement conforme aux spécifications. En fait, il n’y a aucun usage de variable , mais des affectations. Et le compilateur JIT ira encore plus loin, cela éliminera de telles constructions. En toute honnêteté, je ne comprends pas comment ce code est connecté aux spécifications de JLS concernant l’initialisation et l’utilisation des variables. Aucune utilisation, aucun problème. 😉

Veuillez corriger si je me trompe. Je ne peux pas comprendre pourquoi d’autres réponses, qui font référence à de nombreux paragraphes JLS, recueillent autant d’avantages. Ces paragraphes n’ont rien en commun avec ce cas. Juste deux missions en série et pas plus.

Si on écrit:

 int b, c, d, e, f; int a = b = c = d = e = f = 5; 

est égal à:

 f = 5 e = 5 d = 5 c = 5 b = 5 a = 5 

L’expression la plus à droite est simplement assignée aux variables une par une, sans aucune récursivité. Nous pouvons gâcher les variables comme nous le souhaitons:

 a = b = c = f = e = d = a = a = a = a = a = e = f = 5; 

In int x = x + 1; vous ajoutez 1 à x, alors quelle est la valeur de x , elle n’est pas encore créée.

Mais dans int x=x=1; comstackra sans erreur car vous affectez 1 à x .

Votre premier morceau de code contient un second = au lieu d’un plus. Cela comstackra n’importe où alors que le deuxième morceau de code ne sera pas compilé à aucun endroit.

Dans le deuxième morceau de code, x est utilisé avant sa déclaration, tandis que dans le premier morceau de code, il est simplement assigné deux fois, ce qui n’a pas de sens mais est valide.

Décomposons pas à pas, droit associatif

 int x = x = 1 

x = 1 , assigne 1 à une variable x

int x = x , atsortingbue ce que x est à lui-même, en tant qu’int. Puisque x était précédemment atsortingbué à 1, il conserve 1, bien que de manière redondante.

Cela comstack bien.

 int x = x + 1 

x + 1 , ajoutez-en une à une variable x. Cependant, x étant indéfini, cela provoquera une erreur de compilation.

int x = x + 1 , donc cette ligne comstack les erreurs car la partie droite des égales ne sera pas compilée en ajoutant une à une variable non assignée

Le second int x=x=1 est compilé car vous affectez la valeur au x mais dans le cas contraire int x=x+1 ici la variable x n’est pas initialisée, Mémoriser dans la variable locale Java n’est pas initialisée à la valeur par défaut. Note Si elle est ( int x=x+1 ) dans la scope de la classe, elle donnera également une erreur de compilation car la variable n’est pas créée.

 int x = x + 1; 

comstack avec succès dans Visual Studio 2008 avec avertissement

 warning C4700: uninitialized local variable 'x' used` 

x n’est pas initialisé en x = x + 1 ;

Le langage de programmation Java est de type statique, ce qui signifie que toutes les variables doivent d’abord être déclarées avant de pouvoir être utilisées.

Voir les types de données primitifs

La ligne de code ne comstack pas avec un avertissement en raison du fonctionnement réel du code. Lorsque vous exécutez le code int x = x = 1 , Java crée d’abord la variable x , telle que définie. Ensuite, il exécute le code d’affectation ( x = 1 ). Puisque x est déjà défini, le système n’a pas d’erreurs en définissant x sur 1. Cela renvoie la valeur 1, car il s’agit désormais de la valeur de x . Par conséquent, x est maintenant défini sur 1.
Java exécute essentiellement le code comme s’il s’agissait de ceci:

 int x; x = (x = 1); // (x = 1) returns 1 so there is no error 

Cependant, dans votre deuxième morceau de code, int x = x + 1 , l’instruction + 1 nécessite que x soit défini, ce qui ne l’est plus. Étant donné que les instructions d’affectation signifient toujours que le code à droite de = est exécuté en premier, le code échouera car x n’est pas défini. Java exécuterait le code comme ceci:

 int x; x = x + 1; // this line causes the error because `x` is undefined 

Complier a lu les déclarations de droite à gauche et nous avons conçu le contraire. C’est pourquoi cela a agacé au début. Faites-en un habbit pour lire les déclarations (code) de droite à gauche, vous n’aurez pas ce problème.