web-dev-qa-db-fra.com

Étrange Java comportement avec qualificatifs statiques et finaux

Dans notre équipe, nous avons trouvé un comportement étrange où nous utilisions les qualificatifs static et final. Voici notre classe de test:

public class Test {

    public static final Test me = new Test();
    public static final Integer I = 4;
    public static final String S = "abc";

    public Test() {
        System.out.println(I);
        System.out.println(S);
    }

    public static Test getInstance() { return me; }

    public static void main(String[] args) {
        Test.getInstance();
    }
} 

Lorsque nous exécutons la méthode main, nous obtenons un résultat de:

null
abc

Je comprendrais s'il écrivait les valeurs null les deux fois, car le code des membres de la classe statique est exécuté de haut en bas.

Quelqu'un peut-il expliquer pourquoi ce comportement se produit?

76
srnjak

Voici les étapes suivies lorsque vous exécutez votre programme:

  1. Avant d'exécuter main, la classe Test doit être initialisée en exécutant des initialiseurs statiques par ordre d'apparition.
  2. Pour initialiser le champ me, commencez à exécuter new Test().
  3. Affiche la valeur de I. Puisque le type de champ est Integer, ce qui semble être une constante de temps de compilation 4 Devient une valeur calculée (Integer.valueOf(4)). L'initialiseur de ce champ n'est pas encore exécuté, imprimant la valeur initiale null.
  4. Affiche la valeur de S. Comme elle est initialisée avec une constante de compilation, cette valeur est intégrée dans le site de référence, imprimant abc.
  5. new Test() se termine, maintenant l'initialiseur de I s'exécute.

Leçon: si vous comptez sur des singletons statiques initialisés avec impatience, placez la déclaration singleton comme dernière déclaration de champ statique ou recourez à un bloc d'initialisation statique qui se produit après toutes les autres déclarations statiques. Cela fera apparaître la classe entièrement initialisée au code de construction du singleton.

109
Marko Topolnik

S est une constante de temps de compilation, suivant les règles de JLS 15.28 . Ainsi, toute occurrence de S dans le code est remplacée par la valeur connue au moment de la compilation.

Si vous changez le type de I en int, vous verrez la même chose pour cela aussi.

71
Jon Skeet

Vous avez un comportement étrange en raison du type de données Integer. Concernant JLS 12.4.2 les champs statiques sont initialisés dans l'ordre dans lequel vous les écrivez, MAIS les constantes au moment de la compilation sont initialisées en premier.

Si vous n'utilisez pas le type d'encapsuleur Integer mais le type int, vous obtenez le comportement souhaité.

21
siebenschlaefer

Votre Test se compile en:

public class Test {

    public static final Test me;
    public static final Integer I;
    public static final String S = "abc";

    static {
        me = new Test();
        I = Integer.valueOf(4);
    }

    public Test() {
        System.out.println(I);
        System.out.println("abc");
    }

    public static Test getInstance() { return me; }

    public static void main(String[] args) {
        Test.getInstance();
    }
}

Comme vous pouvez le voir, le constructeur de Test est appelé avant l'initialisation de I. C'est pourquoi il imprime "null" pour I. Si vous deviez échanger l'ordre de déclaration pour me et I, vous obtiendriez le résultat attendu car I serait initialisé avant l'appel du constructeur. Vous pouvez également changer le type de I de Integer en int.

Car 4 doit être mis en boîte automatique (c'est-à-dire enveloppé dans un objet Integer), ce n'est pas une constante au moment de la compilation et fait partie du bloc d'initialisation statique. Cependant, si le type était int, le nombre 4 serait une constante au moment de la compilation, il n'aurait donc pas besoin d'être explicitement initialisé. Car "abc" est une constante au moment de la compilation, la valeur de S est imprimée comme prévu.

Si vous voulez remplacer,

public static final String S = "abc";

avec,

public static final String S = new String("abc");

Vous remarquerez alors que la sortie de S est "null" ainsi que. Pourquoi cela se produit-il? Pour la même raison pour laquelle I génère également "null". Les champs comme ceux-ci qui ont des valeurs constantes littérales (qui pas nécessitent une autoboxing, comme String) sont attribués avec le "ConstantValue" attribut lors de la compilation, ce qui signifie que leur valeur peut être résolue simplement en examinant le pool constant de la classe, sans avoir à exécuter de code.

14
Martin Tuskevicius