web-dev-qa-db-fra.com

Pourquoi ce code Java compile-t-il?

Dans la portée de la méthode ou de la classe, la ligne ci-dessous se compile (avec avertissement):

int x = x = 1;

Dans la portée de la classe, où les variables obtiennent leurs valeurs par défaut, ce qui suit donne l'erreur '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 "référence non définie"? Ou peut-être la deuxième ligne int x = x + 1 devrait compiler? Ou il me manque quelque chose?

96
Marcin

tl; dr

Pour champs, int b = b + 1 Est illégal car b est 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 se compile sans se plaindre.

Pour 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 tentant 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, à cause de la différence de sémantique.

Introduction

Tout d'abord, les règles pour les 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 tout au long:

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 erreur illegal forward reference.
La déclaration de d n'est pas valide et échoue avec une erreur variable d might not have been initialized.

Le fait que ces erreurs soient différentes devrait laisser entendre 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.

Le scope d'un champ est défini dans JLS §6. , Portée d'une déclaration.

Les règles pertinentes sont les suivantes:

  • La portée 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 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 ont une portée. Voir §8.3.2.3 pour les règles précises régissant la référence directe aux variables d'instance.

Le §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 réunies:

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

Vous pouvez réellement vous référer aux champs avant qu'ils ne soient 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, des initialisations circulaires ou mal formulées".

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

En bref, les règles disent essentiellement que vous doit 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 (aucun qualificatif comme this.) et (d) il n'est pas accessible depuis une classe interne. Ainsi, une référence directe qui satisfait aux 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 car il viole (b): la référence a est étant assignée à, il est donc légal de se référer à a à l'avance de la déclaration complète de a.

int b = this.b + 1 Compile également car il viole (c): la référence this.b N'est pas un simple nom (elle est qualifiée avec this.). Cette construction étrange est toujours parfaitement bien définie, car this.b A la valeur zéro.

Donc, fondamentalement, les restrictions sur les références de champ dans les initialiseurs empêchent la compilation réussie de int a = a + 1.

Notez que la déclaration de champ int b = (b = 1) + b sera échec à compiler, 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 de variables locales.

Le scope d'une variable locale est défini dans JLS §6. , 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éclarant à 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 int d = d + 1; Ne compile pas?

La raison est due à la règle de Java sur l'affectation définie ( JLS §16 ). L'affectation définie dit essentiellement que chaque accès à une variable locale doit avoir une affectation précédente à cette variable, et le Java vérifie les boucles et les branches pour s'assurer que l'affectation toujours = se produit avant toute utilisation (c'est pourquoi l'affectation définitive a une section de spécifications entière qui lui est dédiée). 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, sinon une erreur de compilation se produit.

Dans int d = d + 1;, L'accès à d est résolu à la variable locale fine, mais comme d n'a pas été affecté avant d'accéder à d, le compilateur émet des problèmes 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; will compile avec succès ( contrairement à la déclaration de champ int b = (b = 1) + b), car d est définitivement attribué au moment où le d final est atteint.

101
nneonneo
int x = x = 1;

est équivalent à

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

en

int x = x + 1; 

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

86
msam

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

int x;
x = 1;
x = 1;

D'abord, int <var> = <expression>; est toujours équivalent à

int <var>;
<var> = <expression>;

Dans ce cas, votre expression est x = 1, qui est également une déclaration. x = 1 est une instruction valide, car le var x a déjà été déclaré. Il s'agit également d'une expression avec la valeur 1, qui est ensuite à nouveau affectée à x.

41
OpenSauce

En Java ou dans n'importe quel langage 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.
12

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 renvoyer une erreur de référence non définie. Il y a maintenant utilisation de variable avant son initialisation, donc ce code est entièrement conforme aux spécifications. En fait, il n'y a aucune utilisation de variable, juste des affectations. Et le compilateur JIT ira encore plus loin, il éliminera de telles constructions. Pour être honnête, je ne comprends pas comment ce code est connecté à la spécification JLS d'initialisation et d'utilisation des variables. Pas d'utilisation, pas de problèmes. ;)

Veuillez corriger si je me trompe. Je ne peux pas comprendre pourquoi d'autres réponses, qui se réfèrent à de nombreux paragraphes JLS, collectent autant d'avantages. Ces paragraphes n'ont rien de commun avec cette affaire. Juste deux affectations en série et pas plus.

Si nous écrivons:

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 juste affectée aux variables une par une, sans aucune récursivité. Nous pouvons salir les variables comme nous le souhaitons:

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

Dans int x = x + 1; vous ajoutez 1 à x, alors quelle est la valeur de x, il n'est pas encore créé.

Mais en int x=x=1; sera compilé sans erreur car vous affectez 1 à x.

7
Alya'a Gamal

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 attribué deux fois, ce qui n'a pas de sens mais est valide.

5
WilQu

Décomposons-le étape par étape, à droite associative

int x = x = 1

x = 1, attribue 1 à une variable x

int x = x, attribue ce que x est à lui-même, en tant qu'int. Étant donné que x était précédemment attribué à 1, il conserve 1, mais de manière redondante.

Cela compile très bien.

int x = x + 1

x + 1, ajoutez-en un à une variable x. Cependant, x étant non défini, cela entraînera une erreur de compilation.

int x = x + 1, cette ligne compile donc les erreurs car la partie droite des égaux ne compile pas en ajoutant une à une variable non affectée

5
steventnorris

Votre premier morceau de code contient un deuxième = au lieu d'un plus. Cela se compilera n'importe où tandis que le deuxième morceau de code ne se compilera à aucun endroit.

5
Joe Elleson

Le deuxième int x=x=1 est compilé car vous assignez la valeur au x mais dans les autres cas int x=x+1 ici la variable x n'est pas initialisée, n'oubliez pas dans Java n'est pas initialisée à la valeur par défaut. Remarque Si c'est (int x=x+1) dans la portée de la classe également alors il donnera une erreur de compilation car la variable n'est pas créée.

3
Krushna
int x = x + 1;

se compile avec succès dans Visual Studio 2008 avec avertissement

warning C4700: uninitialized local variable 'x' used`
2
izogfif

x n'est pas initialisé dans 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 types de données primitifs

2
Mohan Raj B

La ligne de code ne se compile pas avec un avertissement en raison de la façon dont le code fonctionne réellement. Lorsque vous exécutez le code int x = x = 1, Java crée d'abord la variable x, comme défini. Ensuite il exécute le code d'affectation ( x = 1). Puisque x est déjà défini, le système n'a aucune erreur en définissant x sur 1. Ceci renvoie la valeur 1, car c'est maintenant la valeur de x. À cet effet, x est désormais 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 la définition de x, ce qui n'est pas le cas. É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
2
cpdt