web-dev-qa-db-fra.com

Pourquoi tenter d'imprimer une variable non initialisée n'entraîne pas toujours un message d'erreur

Certains peuvent trouver cela similaire à la SO question Will Java Les variables finales ont-elles des valeurs par défaut? mais cette réponse ne résout pas complètement ceci, car cette question n'imprime pas directement la valeur de x dans le bloc d'initialisation d'instance.

Le problème se pose lorsque j'essaie d'imprimer x directement à l'intérieur du bloc d'initialisation d'instance, tout en ayant affecté une valeur à x avant la fin du bloc:

Cas 1

class HelloWorld {

    final int x;

    {
        System.out.println(x);
        x = 7;
        System.out.println(x);    
    }

    HelloWorld() {
        System.out.println("hi");
    }

    public static void main(String[] args) {
        HelloWorld t = new HelloWorld();
    }
}

Cela donne une erreur de temps de compilation indiquant que la variable x n'a peut-être pas été initialisée.

$ javac HelloWorld.Java
HelloWorld.Java:6: error: variable x might not have been initialized
        System.out.println(x);
                           ^
1 error

Cas 2

Au lieu d'imprimer directement, j'appelle une fonction pour imprimer:

class HelloWorld {

    final int x;

    {
        printX();
        x = 7;
        printX();
    }

    HelloWorld() {
        System.out.println("hi");
    }

    void printX() {
        System.out.println(x);
    }

    public static void main(String[] args) {
        HelloWorld t = new HelloWorld();
    }
}

Cela se compile correctement et donne une sortie

0
7
hi

Quelle est la différence conceptuelle entre les deux cas?

55

Dans le JLS, §8.3.3. Références directes pendant l'initialisation du champ , il est indiqué qu'il y a une erreur au moment de la compilation lorsque:

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. Plus précisément, il s'agit d'une erreur de compilation si toutes les conditions suivantes sont remplies:

  • La déclaration d'une variable d'instance dans une classe ou une interface C apparaît textuellement après une utilisation de la variable d'instance;

  • L'utilisation est un nom simple dans un initialiseur de variable d'instance de C ou un initialiseur d'instance de C;

  • L'utilisation n'est pas du côté gauche d'une affectation;

  • C est la classe ou l'interface la plus interne entourant l'utilisation.

Les règles suivantes sont accompagnées de quelques exemples, dont le plus proche du vôtre est celui-ci:

class Z {
    static int peek() { return j; }
    static int i = peek();
    static int j = 1;
}
class Test {
    public static void main(String[] args) {
        System.out.println(Z.i);
    }
}

Les accès [aux variables statiques ou d'instance] par les méthodes ne sont pas vérifiés de cette façon , donc le code ci-dessus produit la sortie 0, Car le l'initialiseur de variable pour i utilise la méthode de classe peek() pour accéder à la valeur de la variable j avant que j ait été initialisé par son initialiseur de variable, auquel pointez qu'il a toujours sa valeur par défaut ( §4.12.5 Valeurs initiales des variables ).

Donc, pour résumer, votre deuxième exemple se compile et s'exécute correctement, car le compilateur ne vérifie pas si la variable x a déjà été initialisée lorsque vous appelez printX() et lorsque printX() se déroule réellement au Runtime, la variable x sera assignée avec sa valeur par défaut (0).

32
Konstantin Yovkov

En lisant le JLS, la réponse semble être dans section 16.2.2 :

Un champ membre final vide V est définitivement attribué (et en outre n'est pas définitivement non affecté) avant le bloc (§14.2) qui est le corps de toute méthode dans la portée de V et avant la déclaration de toute classe déclarée dans le cadre de V.

Cela signifie que lorsqu'une méthode est appelée, le champ final est affecté à sa valeur par défaut 0 avant de l'invoquer, donc lorsque vous la référencez à l'intérieur de la méthode, elle se compile avec succès et imprime la valeur 0.

Cependant, lorsque vous accédez au champ en dehors d'une méthode, il est considéré comme non affecté, d'où l'erreur de compilation. Le code suivant ne sera pas non plus compilé:

public class Main {
    final int x;
    {
        method();
        System.out.println(x);
        x = 7;
    }
    void method() { }
    public static void main(String[] args) { }
}

car:

  • V est [un] attribué avant toute autre instruction S du bloc ssi V est [un] attribué après l'instruction précédant immédiatement S dans le bloc .

Étant donné que le champ final x n'est pas affecté avant l'invocation de la méthode, il n'est toujours pas affecté après.

Cette note dans le JLS est également pertinente:

Notez qu'aucune règle ne nous permettrait de conclure que V n'est définitivement pas affecté avant le bloc qui est le corps de tout constructeur, méthode, initialiseur d'instance ou initialiseur statique déclaré dans C. Nous pouvons conclure de manière informelle que V n'est pas définitivement désaffecté avant le bloc qui est le corps de tout constructeur, méthode, initialiseur d'instance ou initialiseur statique déclaré en C, mais il n'est pas nécessaire qu'une telle règle soit énoncée explicitement.

12
Tunaki

La différence est que dans le premier cas, vous appelez System.out.println from initializer block donc le bloc qui est invoqué avant le constructeur. En première ligne

System.out.println(x);

la variable x n'est pas encore initialisée de sorte que vous obtenez une erreur de compilation.

Mais dans le second cas, vous appelez la méthode d'instance qui ne sait pas si la variable a déjà été initialisée, vous n'avez donc pas d'erreur de compilation et vous pouvez voir la valeur par défaut pour x

4
k0ner

Ok, voici mes 2 cents.

Nous savons tous que les variables finales ne peuvent être initialisées que lors de la déclaration ou ultérieurement dans les constructeurs. Gardant ce fait à l'esprit, voyons ce qui s'est passé jusqu'ici.

Aucune erreur Cas:

Ainsi, lorsque vous utilisez à l'intérieur d'une méthode, elle a déjà une valeur.

 1) If you initialize it, that value.
 2) If not, the default value of data type. 

Cas d'erreur:

Lorsque vous faites cela dans un bloc d'initialisation, vous voyez des erreurs.

Si vous regardez le docs of initialization block

{
    // whatever code is needed for initialization goes here
}

et

Le Java copie les blocs d'initialisation dans chaque constructeur. Par conséquent, cette approche peut être utilisée pour partager un bloc de code entre plusieurs constructeurs.

Aux yeux du compilateur, votre code est littéralement égal à

class HelloWorld {

    final int x;
    HelloWorld() {
        System.out.println(x);  ------------ ERROR here obviously
        x = 7;
        System.out.println(x);  
        System.out.println("hi");
    }

    public static void main(String[] args) {
        HelloWorld t = new HelloWorld();
    }
}

Vous l'utilisez avant même de l'initialiser.

4
Suresh Atta

Cas 1:

Vous donne une erreur de compilation,

Parce qu'à System.out.println(x);

vous essayez d'imprimer x qui n'a jamais été initialisé.

Cas 2:

Fonctionne parce que vous n'utilisez pas directement de valeurs littérales, au lieu de cela, vous appelez une méthode, ce qui est correct.

La règle générale est,

Si vous essayez d'accéder à une variable qui n'est jamais initialisée, cela donnera une erreur de compilation.

1
vishal gajera

Nous traitons ici du bloc d'initialisation. Le compilateur Java copie les blocs d'initialisation dans chaque constructeur.

L'erreur du compilateur ne se produit pas dans le deuxième exemple, car l'impression de x est dans un autre cadre, veuillez vous référer aux spécifications.

0
Krzysztof Szewczyk