web-dev-qa-db-ja.com

静的修飾子と静的ブロックの違い

誰かが私に次の2つのステートメントの違いを説明しますか?

static final変数はstaticコードブロックによって初期化されました:

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

static final代入によって初期化された変数:

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

この例では、1つの微妙な違いがあります。最初の例では、fooはコンパイル時の定数であるとは判断されません。そのため、switchブロックでケースとして使用することはできません(他のコードにインライン化されません)。あなたの2番目の例ではそれです。だから例えば:

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

これは、fooが定数式であると見なされた場合に有効ですが、静的な最終変数である場合には無効です。

ただし、静的な初期化子ブロックは通常使用されます。これは、コレクションに入力するなど、より複雑な初期化コードがある場合に使用されます。

初期化のtimingJLS 12.4.2 で説明されています。コンパイル時の定数と見なされるstatic finalフィールドは最初に初期化され(ステップ6)、初期化子は後で実行されます(ステップ9)。すべての初期化子(フィールド初期化子でも静的初期化子でも)はテキスト順に実行されます。

100
Jon Skeet
 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自体の一部として使用できます。

34
TheLostMind

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
}

逆アセンブリで注意すべき点は、XYの違いが構文糖よりも深く実行されることを示しています。

  • 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定数変数をあまり使用しないことをお勧めします。

要は、公開ライブラリが定数変数を公開する場合、新しいライブラリバージョンがコードと互換性があると想定されている場合は、絶対に変更しないでくださいライブラリの古いバージョンに対してコンパイルされました。必ずしもエラーが発生するわけではありませんが、既存のコードは定数の値に関する古い考えを持っているため、おそらく誤動作します。 (とにかく新しいライブラリバージョンを使用して再コンパイルするクラスが必要な場合、定数を変更してもこの問題は発生しません。)

したがって、定数をブロックで初期化すると、コンパイラが値を他のクラスに埋め込むことができなくなるため、その値を変更する自由度が増します。

8
Boann

唯一の違いは初期化時間です。

Javaは最初にメンバーを初期化し、次に静的ブロックを初期化します。

2
David Limkys

追加の側面:複数の静的フィールドがある場合を考えてください。そうです、これはコーナーケースです...

Jon Skeetの回答で述べたように、JLSは初期化の正確な順序を定義します。ただし、何らかの理由で特定の順序で複数の静的属性を初期化する必要がある場合は、初期化シーケンスをコードで明確に表示することができます。直接フィールド初期化を使用する場合:一部のコードフォーマッタ(および開発者)は、ある時点でフィールドを別の方法でソートすることを決定する場合があります。これは、フィールドの初期化方法に直接影響し、望ましくない影響をもたらします。

ちなみに、一般的なJavaコーディング規約に従う場合は、「定数」(最後の静的フィールド)を定義するときに大文字を使用する必要があります。

--- Jon Skeetのコメントを反映して編集---

2