誰かが私に次の2つのステートメントの違いを説明しますか?
static final
変数はstatic
コードブロックによって初期化されました:
private static final String foo;
static { foo = "foo"; }
static final
代入によって初期化された変数:
private static final String foo = "foo";
この例では、1つの微妙な違いがあります。最初の例では、foo
はコンパイル時の定数であるとは判断されません。そのため、switch
ブロックでケースとして使用することはできません(他のコードにインライン化されません)。あなたの2番目の例ではそれです。だから例えば:
switch (args[0]) {
case foo:
System.out.println("Yes");
break;
}
これは、foo
が定数式であると見なされた場合に有効ですが、静的な最終変数である場合には無効です。
ただし、静的な初期化子ブロックは通常使用されます。これは、コレクションに入力するなど、より複雑な初期化コードがある場合に使用されます。
初期化のtimingは JLS 12.4.2 で説明されています。コンパイル時の定数と見なされるstatic finalフィールドは最初に初期化され(ステップ6)、初期化子は後で実行されます(ステップ9)。すべての初期化子(フィールド初期化子でも静的初期化子でも)はテキスト順に実行されます。
private static final String foo;
static { foo ="foo";}
foo
の値は、クラスがloadedおよびstatic initializers is runの場合initializedです。
private static final String foo = "foo";
ここで、foo
の値はcompile-time定数になります。したがって、実際には"foo"
は、byte-code自体の一部として使用できます。
JLSは、それが呼び出す変数定数変数のいくつかの特別な動作を記述します。これは、final
変数(static
かどうかに関係なく)であり、String
の定数式またはプリミティブ型で初期化されます。
定数変数には、バイナリ互換性に関して大きな違いがあります。コンパイラに関する限り、定数変数のvaluesはクラスのAPIの一部になります。
例:
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);
}
}
ここで、XFOO
は「定数変数」であり、YFOO
はそうではありませんが、それ以外は同等です。クラスZ
はそれらのそれぞれを出力します。それらのクラスをコンパイルし、次にjavap -v X Y Z
で逆アセンブルします。出力は次のとおりです。
クラス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
}
クラスY:
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
}
クラス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
}
逆アセンブリで注意すべき点は、X
とY
の違いが構文糖よりも深く実行されることを示しています。
XFOO
には ConstantValue
属性があり、その値がコンパイル時の定数であることを示します。これに対して、YFOO
はそうではなく、static
ブロックをputstatic
命令とともに使用して、実行時に値を初期化します。
String
定数"xfoo"
は、クラスZ
の定数プールの一部になりましたが、"yfoo"
はそうではありません。
Z.main
は、ldc
(定数ロード)命令を使用して"xfoo"
を独自の定数プールからスタックに直接ロードしますが、getstatic
命令を使用してY.YFOO
の値をロードします。
あなたが見つける他の違い:
XFOO
の値を変更し、X.Java
ではなくZ.Java
を再コンパイルすると、問題が発生します。クラスZ
はまだ古い値を使用しています。 YFOO
の値を変更してY.Java
を再コンパイルすると、Z.Java
を再コンパイルするかどうかにかかわらず、クラスZ
は新しい値を使用します。
X.class
ファイルを完全に削除しても、クラスZ
は引き続き正しく実行されます。 Z
には、X
に対する実行時の依存関係はありません。Y.class
ファイルを削除すると、クラスZ
は、ClassNotFoundException: Y
で初期化できません。
Javadocを使用してクラスのドキュメントを生成する場合、[定数フィールド値]ページにはXFOO
の値は記載されますが、YFOO
の値は記載されません。
JLSは、定数変数がコンパイル済みクラスファイルに及ぼす上記の影響を §13.1. で説明しています。
定数変数(§4.12.4)であるフィールドへの参照は、コンパイル時に定数変数の初期化子によって示される値Vに解決される必要があります。
そのようなフィールドが
static
である場合、フィールドを宣言したクラスまたはインターフェースを含め、フィールドへの参照がバイナリファイル内のコードに存在してはなりません。そのようなフィールドは常に初期化されているように見える必要があります(§12.4.2)。フィールドのデフォルトの初期値(Vと異なる場合)は絶対に遵守してはなりません。そのようなフィールドが
static
でない場合、フィールドを含むクラスを除き、フィールドへの参照はバイナリファイル内のコードに存在してはなりません。 (インターフェイスにはstatic
フィールドしかないため、インターフェイスではなくクラスになります。)クラスには、インスタンスの作成中にフィールドの値をVに設定するコードが必要です(§12.5)。
そして §13.4.9 :
フィールドが定数変数(§4.12.4)であり、さらに
static
である場合、キーワードfinal
を削除したり、その値を変更したりしても、既存のバイナリとの互換性が失われることはありませんが、実行されません。再コンパイルされない限り、フィールドの使用法の新しい値。[...]
広く分散しているコードで「定数定数」の問題を回避する最良の方法は、
static
定数変数を、本当に変更される可能性が低い値に対してのみ使用することです。真の数学定数の場合を除いて、ソースコードではstatic
定数変数をあまり使用しないことをお勧めします。
要は、公開ライブラリが定数変数を公開する場合、新しいライブラリバージョンがコードと互換性があると想定されている場合は、絶対に変更しないでくださいライブラリの古いバージョンに対してコンパイルされました。必ずしもエラーが発生するわけではありませんが、既存のコードは定数の値に関する古い考えを持っているため、おそらく誤動作します。 (とにかく新しいライブラリバージョンを使用して再コンパイルするクラスが必要な場合、定数を変更してもこの問題は発生しません。)
したがって、定数をブロックで初期化すると、コンパイラが値を他のクラスに埋め込むことができなくなるため、その値を変更する自由度が増します。
唯一の違いは初期化時間です。
Javaは最初にメンバーを初期化し、次に静的ブロックを初期化します。
追加の側面:複数の静的フィールドがある場合を考えてください。そうです、これはコーナーケースです...
Jon Skeetの回答で述べたように、JLSは初期化の正確な順序を定義します。ただし、何らかの理由で特定の順序で複数の静的属性を初期化する必要がある場合は、初期化シーケンスをコードで明確に表示することができます。直接フィールド初期化を使用する場合:一部のコードフォーマッタ(および開発者)は、ある時点でフィールドを別の方法でソートすることを決定する場合があります。これは、フィールドの初期化方法に直接影響し、望ましくない影響をもたらします。
ちなみに、一般的なJavaコーディング規約に従う場合は、「定数」(最後の静的フィールド)を定義するときに大文字を使用する必要があります。
--- Jon Skeetのコメントを反映して編集---