web-dev-qa-db-fra.com

Différence entre le modificateur statique et le bloc statique

Quelqu'un m'explique les différences entre les deux déclarations suivantes?

UNE static final variable initialisée par un bloc de code static:

private static final String foo;
static { foo = "foo"; }

UNE static final variable initialisée par une affectation:

private static final String foo = "foo";
83
Fabio Marano

Dans cet exemple, il y a une différence subtile - dans votre premier exemple, foo n'est pas déterminé comme étant une constante au moment de la compilation, il ne peut donc pas être utilisé comme cas dans les blocs switch (et ne serait pas inséré dans un autre code); dans votre deuxième exemple, c'est. Ainsi, par exemple:

switch (args[0]) {
    case foo:
        System.out.println("Yes");
        break;
}

Cela est valable lorsque foo est considéré comme une expression constante, mais pas lorsqu'il s'agit "juste" d'une variable finale statique.

Cependant, les blocs d'initialisation statiques sont généralement utilisés lorsque vous avez un code d'initialisation plus compliqué - tel que le remplissage d'une collection.

Le timing pour l'initialisation est décrit dans JLS 12.4.2 ; tous les champs finaux statiques qui sont considérés comme des constantes au moment de la compilation sont initialisés en premier (étape 6) et les initialiseurs sont exécutés plus tard (étape 9); tous les initialiseurs (qu'ils soient des initialiseurs de champ ou des initialiseurs statiques) sont exécutés dans l'ordre textuel.

100
Jon Skeet
 private static final String foo;
 static { foo ="foo";}

La valeur de foo est initialisée lorsque la classe est chargée et les initialiseurs statiques sont exécutés.

private static final String foo = "foo";

Ici, la valeur de foo sera une constante au moment de la compilation. Donc, en réalité "foo" sera disponible dans le cadre de th byte-code lui-même.

34
TheLostMind

Dans le IIème cas, la valeur de foo est une liaison anticipée ie le compilateur identifie et affecte la valeur foo à la variable FOO, qui ne peut pas être modifiée, et ce sera disponible séparément avec octet -code lui-même.

private static final String FOO = "foo";

et Dans la première valeur de cas de foo initialize juste après le chargement de la classe comme toute première affectation avant que la variable d'instance ne soit affectée, ici aussi vous pouvez intercepter des exceptions ou le champ statique peut être - attribuer en appelant des méthodes statiques dans un bloc statique.

private static final String FOO;
static { FOO ="foo";}

Donc, chaque fois qu'une condition arrive lorsque le compilateur doit identifier la valeur de la variable foo, la condition II fonctionnera, par exemple -comme la valeur de case: dans les cas de commutation.

10
user4768611

Le JLS décrit quelques comportements spéciaux de ce qu'il appelle variables constantes, qui sont final variables (que ce soit static ou non) qui sont initialisées avec des expressions constantes de String ou type primitif.

Les variables constantes ont une différence majeure en ce qui concerne la compatibilité binaire: les valeurs des variables constantes font partie de l'API de la classe, en ce qui concerne le compilateur.

Un exemple:

class X {
    public static final String XFOO = "xfoo";
}

class Y {
    public static final String YFOO;
    static { YFOO = "yfoo"; }
}

class Z {
    public static void main(String[] args) {
        System.out.println(X.XFOO);
        System.out.println(Y.YFOO);
    }
}

Ici, XFOO est une "variable constante" et YFOO ne l'est pas, mais ils sont par ailleurs équivalents. La classe Z imprime chacun d'eux. Compilez ces classes, puis démontez-les avec javap -v X Y Z, et voici la sortie:

Classe X:

Constant pool:
   #1 = Methodref          #3.#11         //  Java/lang/Object."<init>":()V
   #2 = Class              #12            //  X
   #3 = Class              #13            //  Java/lang/Object
   #4 = Utf8               XFOO
   #5 = Utf8               Ljava/lang/String;
   #6 = Utf8               ConstantValue
   #7 = String             #14            //  xfoo
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = NameAndType        #8:#9          //  "<init>":()V
  #12 = Utf8               X
  #13 = Utf8               Java/lang/Object
  #14 = Utf8               xfoo
{
  public static final Java.lang.String XFOO;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: String xfoo


  X();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method Java/lang/Object."<init>":()V
         4: return
}

Chic:

Constant pool:
   #1 = Methodref          #5.#12         //  Java/lang/Object."<init>":()V
   #2 = String             #13            //  yfoo
   #3 = Fieldref           #4.#14         //  Y.YFOO:Ljava/lang/String;
   #4 = Class              #15            //  Y
   #5 = Class              #16            //  Java/lang/Object
   #6 = Utf8               YFOO
   #7 = Utf8               Ljava/lang/String;
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               <clinit>
  #12 = NameAndType        #8:#9          //  "<init>":()V
  #13 = Utf8               yfoo
  #14 = NameAndType        #6:#7          //  YFOO:Ljava/lang/String;
  #15 = Utf8               Y
  #16 = Utf8               Java/lang/Object
{
  public static final Java.lang.String YFOO;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL


  Y();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method Java/lang/Object."<init>":()V
         4: return

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #2                  // String yfoo
         2: putstatic     #3                  // Field YFOO:Ljava/lang/String;
         5: return
}

Classe Z:

Constant pool:
   #1 = Methodref          #8.#14         //  Java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        //  Java/lang/System.out:Ljava/io/PrintStream;
   #3 = Class              #17            //  X
   #4 = String             #18            //  xfoo
   #5 = Methodref          #19.#20        //  Java/io/PrintStream.println:(Ljava/lang/String;)V
   #6 = Fieldref           #21.#22        //  Y.YFOO:Ljava/lang/String;
   #7 = Class              #23            //  Z
   #8 = Class              #24            //  Java/lang/Object
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = NameAndType        #9:#10         //  "<init>":()V
  #15 = Class              #25            //  Java/lang/System
  #16 = NameAndType        #26:#27        //  out:Ljava/io/PrintStream;
  #17 = Utf8               X
  #18 = Utf8               xfoo
  #19 = Class              #28            //  Java/io/PrintStream
  #20 = NameAndType        #29:#30        //  println:(Ljava/lang/String;)V
  #21 = Class              #31            //  Y
  #22 = NameAndType        #32:#33        //  YFOO:Ljava/lang/String;
  #23 = Utf8               Z
  #24 = Utf8               Java/lang/Object
  #25 = Utf8               Java/lang/System
  #26 = Utf8               out
  #27 = Utf8               Ljava/io/PrintStream;
  #28 = Utf8               Java/io/PrintStream
  #29 = Utf8               println
  #30 = Utf8               (Ljava/lang/String;)V
  #31 = Utf8               Y
  #32 = Utf8               YFOO
  #33 = Utf8               Ljava/lang/String;
{
  Z();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method Java/lang/Object."<init>":()V
         4: return

  public static void main(Java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field Java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #4                  // String xfoo
         5: invokevirtual #5                  // Method Java/io/PrintStream.println:(Ljava/lang/String;)V
         8: getstatic     #2                  // Field Java/lang/System.out:Ljava/io/PrintStream;
        11: getstatic     #6                  // Field Y.YFOO:Ljava/lang/String;
        14: invokevirtual #5                  // Method Java/io/PrintStream.println:(Ljava/lang/String;)V
        17: return
}

Choses à noter dans le démontage, qui vous indiquent les différences entre X et Y plus profondes que le sucre syntaxique:

  • XFOO possède un attribut ConstantValue , ce qui signifie que sa valeur est une constante au moment de la compilation. Ce n'est pas le cas de YFOO et utilise un bloc static avec une instruction putstatic pour initialiser la valeur au moment de l'exécution.

  • La constante String"xfoo" fait désormais partie du pool constant de classe Z, mais "yfoo" non.

  • Z.main utilise l'instruction ldc (constante de charge) pour charger "xfoo" sur la pile directement à partir de son propre pool constant, mais il utilise une instruction getstatic pour charger la valeur de Y.YFOO.

Vous trouverez d'autres différences:

  • Si vous modifiez la valeur de XFOO et recompilez X.Java mais non Z.Java, vous avez un problème: la classe Z utilise toujours l'ancienne valeur. Si vous modifiez la valeur de YFOO et recompilez Y.Java, classe Z utilise la nouvelle valeur si vous recompilez Z.Java ou pas.

  • Si vous supprimez le X.class fichier entièrement, la classe Z fonctionne toujours correctement. Z n'a pas de dépendance d'exécution sur X. Alors que si vous supprimez le Y.class fichier, classe Z ne parvient pas à s'initialiser avec un ClassNotFoundException: Y.

  • Si vous générez de la documentation pour les classes avec javadoc, la page "Valeurs de champ constantes" documentera la valeur de XFOO, mais pas la valeur de YFOO.

Le JLS décrit les effets des variables constantes ci-dessus sur les fichiers de classe compilés dans §13.1. :

Une référence à un champ qui est une variable constante (§4.12.4) doit être résolue au moment de la compilation à la valeur V indiquée par l'initialiseur de la variable constante.

Si un tel champ est static, aucune référence au champ ne doit être présente dans le code dans un fichier binaire, y compris la classe ou l'interface qui a déclaré le champ. Un tel champ doit toujours sembler avoir été initialisé (§12.4.2); la valeur initiale par défaut du champ (si différente de V) ne doit jamais être respectée.

Si un tel champ est non -static, alors aucune référence au champ ne doit être présente dans le code dans un fichier binaire, sauf dans la classe contenant le champ. (Ce sera une classe plutôt qu'une interface, car une interface n'a que des champs static.) La classe doit avoir du code pour définir la valeur du champ sur V lors de la création de l'instance (§12.5).

Et dans §13.4.9 :

Si un champ est une variable constante (§4.12.4), et est en outre static, la suppression du mot clé final ou la modification de sa valeur ne rompra pas la compatibilité avec les binaires préexistants en les provoquant ne pas s'exécuter, mais ils ne verront aucune nouvelle valeur pour une utilisation du champ à moins qu'ils ne soient recompilés.

[...]

La meilleure façon d'éviter les problèmes avec les "constantes inconstantes" dans le code largement distribué est d'utiliser les variables constantes static uniquement pour les valeurs qui ne changeront vraisemblablement jamais. À part les vraies constantes mathématiques, nous recommandons que le code source utilise très peu les variables constantes static.

Le résultat est que si votre bibliothèque publique expose des variables constantes, vous ne devez jamais changer leurs valeurs si votre nouvelle version de bibliothèque est par ailleurs censée être compatible avec le code compilé avec les anciennes versions de la bibliothèque. Cela ne provoquera pas nécessairement une erreur, mais le code existant sera probablement défectueux car il aura des idées obsolètes sur les valeurs des constantes. (Si votre nouvelle version de bibliothèque nécessite de toute façon des classes qui l'utilisent pour être recompilée, la modification des constantes ne pose pas ce problème.)

Ainsi, l'initialisation d'une constante avec un bloc vous donne plus de liberté pour modifier sa valeur, car elle empêche le compilateur d'incorporer la valeur dans d'autres classes.

8
Boann

La seule différence est le temps d'initialisation.

Java initialise d'abord les membres puis les blocs statiques.

2
David Limkys

Un aspect supplémentaire: considérez le cas lorsque vous avez plusieurs champs statiques, et oui c'est un cas d'angle ...

Comme indiqué dans la réponse de Jon Skeet, le JLS définit l'ordre exact d'initialisation. Cependant, si pour une raison quelconque vous devez initialiser plusieurs attributs statiques dans un ordre spécifique, vous souhaiterez peut-être rendre la séquence d'initialisation clairement visible dans le code. Lorsque vous utilisez l'initialisation directe des champs: certains formateurs de code (et développeurs) peuvent décider à un moment donné de trier les champs différemment, cela aura un impact direct sur la façon dont les champs sont initialisés et introduira des effets indésirables.

Soit dit en passant, si vous souhaitez suivre les conventions de codage Java Java courantes), vous devez utiliser des majuscules lors de la définition des "constantes" (champs statiques finaux).

--- édité reflétant les commentaires de Jon Skeet ---

2
Danilo Tommasina