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";
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.
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.
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.
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 champsstatic
.) 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 constantesstatic
.
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.
La seule différence est le temps d'initialisation.
Java initialise d'abord les membres puis les blocs statiques.
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 ---