96 votes

Pourquoi ce code Java compile-t-il ?

Dans le cadre d'une méthode ou d'une classe, la ligne ci-dessous compile (avec un avertissement) :

int x = x = 1;

Dans le cadre de la classe, où les variables obtiennent leurs valeurs par défaut le texte suivant donne l'erreur "référence non définie" :

int x = x + 1;

N'est-ce pas le premier x = x = 1 devrait aboutir à la même erreur de "référence indéfinie" ? Ou peut-être que la deuxième ligne int x = x + 1 devrait compiler ? Ou bien il y a quelque chose qui m'échappe ?

101voto

nneonneo Points 56821

Tl;dr

Pour champs , int b = b + 1 est illégal parce que b est une référence directe illégale à b . Vous pouvez résoudre ce problème en écrivant int b = this.b + 1 qui compile sans se plaindre.

Pour variables locales , int d = d + 1 est illégal parce que d n'est pas initialisé avant son utilisation. Il s'agit no le cas des 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 une déclaration de champ et comme une déclaration de variable locale. La première échouera, mais la seconde réussira, en raison de la différence de sémantique.

Introduction

Tout d'abord, les règles relatives aux initialisateurs de champs et de variables locales sont très différentes. Cette réponse abordera donc ces règles en deux parties.

Nous utiliserons ce programme de test tout au long de l'année :

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

Le fait que ces erreurs soient différentes devrait laisser penser que les raisons de ces erreurs sont également différentes.

Champs

Les initialisateurs de champs en Java sont régis par JLS §8.3.2 Initialisation des champs.

El portée d'un champ est défini dans JLS §6.3 La portée d'une déclaration.

Les règles pertinentes sont :

  • La portée de la déclaration d'un membre m déclaré dans ou hérité par une classe de type C (§8.1.6) est le corps entier de C, y compris toute déclaration de type imbriquée.
  • Les expressions d'initialisation des variables d'instance peuvent utiliser le nom simple de toute variable statique déclarée dans la classe ou héritée par elle, même une variable dont la déclaration est textuellement postérieure.
  • 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 ne doit apparaître textuellement avant son utilisation que si le membre est un champ d'instance (respectivement statique). n'est utilisée que si le membre est un champ d'instance (respectivement statique) de une classe ou une interface C et que toutes les conditions suivantes sont réunies :

  • L'utilisation se produit dans un initialisateur de variable d'instance (respectivement statique) de C ou dans un initialisateur d'instance (respectivement statique) de C.
  • L'usage ne se trouve 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 qui englobe 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 ont pour but d'éviter que du 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 attraper, au moment de la compilation, les initialisations circulaires ou autrement mal formées".

A quoi se résument ces règles en réalité ?

En résumé, les règles disent essentiellement que vous devez doit déclarer un champ avant une référence à ce champ si (a) la référence se trouve dans un initialisateur, (b) la référence n'est pas assignée, (c) la référence est un champ de type nom simple (pas de qualificatifs comme this. ) et (d) on n'y accède pas depuis une classe interne. Ainsi, une référence directe qui remplit les quatre conditions est illégale, mais une référence directe qui ne remplit pas au moins une condition est acceptable.

int a = a = 1; compile parce qu'il viole (b) : la référence a est à laquelle il est assigné, il est donc légal de se référer à a à l'avance de a La déclaration complète de l'entreprise.

int b = this.b + 1 compile également parce qu'il viole (c) : la référence this.b n'est pas un simple nom (il est qualifié avec this. ). Cette construction étrange est encore parfaitement bien définie, car this.b a la valeur zéro.

Donc, en gros, les restrictions sur les références de champ dans les initialisateurs empêchent les int a = a + 1 d'être compilé avec succès.

Observez que la déclaration de champ int b = (b = 1) + b sera échouer pour compiler, car la version finale b est toujours une référence directe illégale.

Variables locales

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

El portée d'une variable locale est définie dans JLS §6.3 La 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 initialisateur et en incluant tout autre déclarateur à droite dans la déclaration de variable locale.

Notez que les initialisateurs sont dans la portée de la variable déclarée. Alors pourquoi int d = d + 1; compiler ?

La raison est due à la règle de Java sur les affectation définitive ( JLS §16 ). L'assignation définitive signifie essentiellement que chaque accès à une variable locale doit être précédé d'une assignation à cette variable, et le compilateur Java vérifie les boucles et les branches pour s'assurer que l'assignation toujours se fait avant toute utilisation (c'est pourquoi l'affectation définitive fait l'objet d'une section entière de spécifications). La règle de base est la suivante :

  • Pour chaque accès à une variable locale ou à un champ final vide x , x doit être définitivement assigné avant l'accès, sinon une erreur de compilation se produit.

Sur int d = d + 1; l'accès à d est résolu à la variable locale fine, mais puisque d n'a pas été attribué avant d est accédé, le compilateur émet une erreur. Dans int c = c = 1 , c = 1 se produit d'abord, qui attribue c et ensuite c est initialisé au résultat de cette affectation (qui est 1).

Notez qu'en raison des règles d'affectation définies, la déclaration de la variable locale int d = (d = 1) + d; sera compiler avec succès ( contrairement à la déclaration de champ int b = (b = 1) + b ), car d est définitivement attribuée au moment où la finale d est atteint.

86voto

msam Points 2883
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 donc vous obtenez une erreur (le compilateur sait que la valeur de x n'est pas connue)

41voto

OpenSauce Points 3241

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

int x;
x = 1;
x = 1;

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

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

Dans ce cas, votre expression est x = 1 qui est aussi une déclaration. x = 1 est une déclaration valide, puisque la var x a déjà été déclaré. C'est aussi une expression avec la valeur 1, qui est ensuite affectée à x encore.

12voto

En java ou dans toute langue moderne, l'affectation vient de la droite.

Supposons que vous ayez deux variables x et y,

int z = x = y = 5;

Cette déclaration est valide et c'est ainsi que le compilateur les divise.

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

Mais dans votre cas

int x = x + 1;

Le compilateur a donné une exception parce que, il se divise comme ceci.

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

7voto

Alya'a Gamal Points 5967

Sur 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; compilera sans erreur parce que vous assignez 1 à x .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X