web-dev-qa-db-ja.com

静的初期化ブロック

私が理解した限りでは、「静的初期化ブロック」は、静的フィールドの値を1行ではできない場合に設定するために使用されます。

しかし、そのために特別なブロックが必要な理由はわかりません。たとえば、フィールドを静的(値の割り当てなし)として宣言します。そして、上記の宣言された静的フィールドに値を生成して割り当てるコードを数行書いてください。

なぜstatic {...}のような特別なブロックにこの行が必要なのですか?

243
Roman

非静的ブロック:

{
    // Do Something...
}

呼び出される毎回クラスのインスタンスが構築されます。 静的ブロックは、クラス自体が初期化されたときに、onceと呼ばれる場合にのみ呼び出されます。 。

例:

public class Test {

    static{
        System.out.println("Static");
    }

    {
        System.out.println("Non-static block");
    }

    public static void main(String[] args) {
        Test t = new Test();
        Test t2 = new Test();
    }
}

これは印刷します:

Static
Non-static block
Non-static block
397

もしそれらが静的初期化ブロックの中になければ、どこにあるのでしょうか?初期化の目的のためだけにローカルであることを意図した変数をどのように宣言し、それをフィールドと区別しますか?たとえば、どのようにあなたを書きたいと思いますか。

public class Foo {
    private static final int widgets;

    static {
        int first = Widgets.getFirstCount();
        int second = Widgets.getSecondCount();
        // Imagine more complex logic here which really used first/second
        widgets = first + second;
    }
}

firstsecondがブロック内にない場合、それらはフィールドのように見えます。それらがその前にstaticを持たないブロック内にあった場合、それは静的初期化ブロックの代わりにインスタンス初期化ブロックとしてカウントされるので、合計で1回ではなくper構築されたインスタンスとして実行されます。

今回のケースでは、代わりに静的メソッドを使用できます。

public class Foo {
    private static final int widgets = getWidgets();

    static int getWidgets() {
        int first = Widgets.getFirstCount();
        int second = Widgets.getSecondCount();
        // Imagine more complex logic here which really used first/second
        return first + second;
    }
}

...しかし、同じブロック内に割り当てたい変数が複数ある場合、または何もしない場合(たとえば、何かを記録したい場合、またはネイティブライブラリを初期化したい場合など)、これは機能しません。

122
Jon Skeet

これが例です:

  private static final HashMap<String, String> MAP = new HashMap<String, String>();
  static {
    MAP.put("banana", "honey");
    MAP.put("peanut butter", "jelly");
    MAP.put("rice", "beans");
  }

"static"セクションのコードは、クラスのインスタンスが構築される前(および他の場所から静的メソッドが呼び出される前)のクラスロード時に実行されます。こうすることで、クラスのリソースがすべて使える状態になっていることを確認できます。

静的でない初期化子ブロックを持つことも可能です。これらは、クラスに定義されている一連のコンストラクタメソッドの拡張のように動作します。キーワード "static"が省略されている点を除けば、静的初期化子ブロックのように見えます。

97
Pointy

実行時にクラスを一度だけロードするなど、実際には値を割り当てたくない場合にも便利です。

例えば。

static {
    try {
        Class.forName("com.example.jdbc.Driver");
    } catch (ClassNotFoundException e) {
        throw new ExceptionInInitializerError("Cannot load JDBC driver.", e);
    }
}

ねえ、別の利点があります、あなたは例外を処理するためにそれを使用することができます。 getStuff()がここで本当にがcatchブロックに属しているExceptionをスローすると想像してみてください。

private static Object stuff = getStuff(); // Won't compile: unhandled exception.

その場合はstaticイニシャライザが役に立ちます。あなたはそこで例外を処理することができます。

別の例は、代入中にはできないことを後で行うことです。

private static Properties config = new Properties();

static {
    try { 
        config.load(Thread.currentThread().getClassLoader().getResourceAsStream("config.properties");
    } catch (IOException e) {
        throw new ExceptionInInitializerError("Cannot load properties file.", e);
    }
}

JDBCドライバの例に戻ると、適切なJDBCドライバ自体もstaticイニシャライザを使用して、自分自身をDriverManagerに登録します。 this および this answerも参照してください。

47
BalusC

static blockは単なる構文上の糖であると思います。 staticブロックを使ってできることは他にはありません。

ここに掲載されている例を再利用する。

このコードは、staticイニシャライザを使用せずに書き直すことができます。

方法1:staticを使う

private static final HashMap<String, String> MAP;
static {
    MAP.put("banana", "honey");
    MAP.put("peanut butter", "jelly");
    MAP.put("rice", "beans");
  }

方法2:staticなし

private static final HashMap<String, String> MAP = getMap();
private static HashMap<String, String> getMap()
{
    HashMap<String, String> ret = new HashMap<>();
    ret.put("banana", "honey");
    ret.put("peanut butter", "jelly");
    ret.put("rice", "beans");
    return ret;
}
11
user1508893

それが存在するために必要とされるいくつかの実際の理由があります。

  1. 初期化によって例外がスローされる可能性があるstatic finalメンバーの初期化
  2. 計算値でstatic finalメンバーを初期化する

特定のクラスがロードされていることを確認するなど、クラスがランタイム内で依存することを初期化する便利な方法としてstatic {}ブロックを使用する傾向があります(例:JDBCドライバ)。それは他の方法でも可能です。ただし、上記の2つのことは、static {}ブロックのような構成要素でしか実行できません。

10
D.Shawley

オブジェクトが静的ブロック内に構築される前に、クラスに対して1回だけコードを実行できます。

例えば。

class A {
  static int var1 = 6;
  static int var2 = 9;
  static int var3;
  static long var4;

  static Date date1;
  static Date date2;

  static {
    date1 = new Date();

    for(int cnt = 0; cnt < var2; cnt++){
      var3 += var1;
    }

    System.out.println("End first static init: " + new Date());
  }
}

静的ブロックは静的フィールドにしかアクセスできないと考えるのはよくある誤解です。このために、実際のプロジェクトでよく使用するコードを以下に示します(少し異なるコンテキストで 別の回答 から部分的にコピーされています)。

public enum Language { 
  ENGLISH("eng", "en", "en_GB", "en_US"),   
  GERMAN("de", "ge"),   
  CROATIAN("hr", "cro"),   
  RUSSIAN("ru"),
  BELGIAN("be",";-)");

  static final private Map<String,Language> ALIAS_MAP = new HashMap<String,Language>(); 
  static { 
    for (Language l:Language.values()) { 
      // ignoring the case by normalizing to uppercase
      ALIAS_MAP.put(l.name().toUpperCase(),l); 
      for (String alias:l.aliases) ALIAS_MAP.put(alias.toUpperCase(),l); 
    } 
  } 

  static public boolean has(String value) { 
    // ignoring the case by normalizing to uppercase
    return ALIAS_MAP.containsKey(value.toUpper()); 
  } 

  static public Language fromString(String value) { 
    if (value == null) throw new NullPointerException("alias null"); 
    Language l = ALIAS_MAP.get(value); 
    if (l == null) throw new IllegalArgumentException("Not an alias: "+value); 
    return l; 
  } 

  private List<String> aliases; 
  private Language(String... aliases) { 
    this.aliases = Arrays.asList(aliases); 
  } 
} 

ここでは、初期化子を使用してインデックス(ALIAS_MAP)を管理し、エイリアスのセットを元の列挙型にマッピングします。これはEnum自身によって提供される組み込みのvalueOfメソッドの拡張として意図されています。

ご覧のとおり、静的初期化子はprivateフィールドaliasesにもアクセスします。 staticブロックはすでにEnumの値インスタンス(例えばENGLISH)にアクセスできることを理解することが重要です。これは、Enumブロックが呼び出される前にstatic privateフィールドがインスタンスで初期化されているのと同様に、 static型の場合の初期化と実行の順序 が原因です。

  1. 暗黙の静的フィールドであるEnum定数。これには、Enumコンストラクターとインスタンスブロック、およびインスタンスの初期化も最初に行われる必要があります。
  2. 発生順にstaticブロックと静的フィールドの初期化。

このアウトオブオーダ初期化(staticブロックの前のコンストラクタ)は注意することが重要です。これは、シングルトンと同様にインスタンスを使用して静的フィールドを初期化したときにも発生します(単純化したもの)。

public class Foo {
  static { System.out.println("Static Block 1"); }
  public static final Foo FOO = new Foo();
  static { System.out.println("Static Block 2"); }
  public Foo() { System.out.println("Constructor"); }
  static public void main(String p[]) {
    System.out.println("In Main");
    new Foo();
  }
}

表示されるのは次の出力です。

Static Block 1
Constructor
Static Block 2
In Main
Constructor

明らかに、静的な初期化は実際に起こる可能性がありますbeforeコンストラクタ、そしてその後でさえも:

MainメソッドでFooにアクセスするだけで、クラスがロードされ、静的初期化が開始されます。しかし、静的初期化の一部として、静的フィールドのコンストラクタを再度呼び出します。その後、静的初期化を再開し、mainメソッド内から呼び出されたコンストラクタを完成させます。やや複雑な状況で、私は通常のコーディングでは対処する必要がないことを願っています。

これに関するより詳しい情報は、 " Effective Java "という本を見てください。

7
YoYo

静的変数を実行時に設定する必要がある場合は、static {...}ブロックが非常に役立ちます。

例えば、静的メンバーを構成ファイルまたはデータベースに保管されている値に設定する必要がある場合です。

初期のメンバー宣言では値を追加できないため、静的なMapメンバーに値を追加する場合にも便利です。

3
Marcus Leon

静的フィールド(クラスのインスタンスではなくクラスに属しているため、つまり、オブジェクトではなくクラスに関連付けられているため、 "クラス変数"とも呼ばれます)を使用して初期化します。したがって、このクラスのインスタンスを作成したくない場合、この静的フィールドを操作したい場合は、次の3つの方法で行うことができます。

1 - 変数を宣言したときに初期化します。

static int x = 3;

2 - 静的初期化ブロックを持っている:

static int x;

static {
 x=3;
}

3 - クラス変数にアクセスして初期化するクラスメソッド(静的メソッド)を用意します。これは上記の静的ブロックの代わりになります。あなたはプライベートな静的メソッドを書くことができます:

public static int x=initializeX();

private static int initializeX(){
 return 3;
}

では、なぜ静的メソッドの代わりに静的初期化ブロックを使用するのでしょうか。

それは本当にあなたのプログラムに必要なもの次第です。しかし、静的初期化ブロックが1回呼び出されることを知っておく必要があります。クラスメソッドの唯一の利点は、クラス変数を再初期化する必要がある場合に後で再利用できることです。

プログラムに複雑な配列があるとしましょう。これを初期化して(たとえばfor loopを使用して)、その後この配列の値はプログラム全体で変化しますが、ある時点で再初期化したい(初期値に戻ります)。この場合、private staticメソッドを呼び出すことができます。プログラムで値を再初期化する必要がない場合は、あとでプログラムで使用するつもりはないので、静的ブロックを使用するだけで静的メソッドを使用する必要はありません。

注:静的ブロックは、コードに現れる順番で呼び出されます。

例1

class A{
 public static int a =f();

// this is a static method
 private static int f(){
  return 3;
 }

// this is a static block
 static {
  a=5;
 }

 public static void main(String args[]) {
// As I mentioned, you do not need to create an instance of the class to use the class variable
  System.out.print(A.a); // this will print 5
 }

}

例2

class A{
 static {
  a=5;
 }
 public static int a =f();

 private static int f(){
  return 3;
 }

 public static void main(String args[]) {
  System.out.print(A.a); // this will print 3
 }

}
2
Randa Sbeity

最初に、アプリケーションクラス自体が実行時にJava.class.Classオブジェクトにインスタンス化されることを理解する必要があります。これはあなたの静的ブロックが実行されるときです。だからあなたは実際にこれを行うことができます:

public class Main {

    private static int myInt;

    static {
        myInt = 1;
        System.out.println("myInt is 1");
    }

    //  needed only to run this class
    public static void main(String[] args) {
    }

}

コンソールに "myInt is 1"と表示されます。私はクラスをインスタンス化していないことに注意してください。

0
eosimosu

補足として、@Pointyが言ったように

"static"セクションのコードは、クラスのインスタンスが構築される前(および他の場所から静的メソッドが呼び出される前)のクラスロード時に実行されます。

静的ブロックにSystem.loadLibrary("I_am_native_library")を追加することになっています。

static{
    System.loadLibrary("I_am_a_library");
}

関連ライブラリがメモリにロードされる前にネイティブメソッドが呼び出されないことが保証されます。

Oracleからの loadLibraryによると

このメソッドが同じライブラリ名で複数回呼び出された場合、2回目以降の呼び出しは無視されます。

非常に意外なことに、System.loadLibraryを置くことはライブラリが複数回ロードされるのを避けるためには使われません。

0
Gearon
static int B,H;
static boolean flag = true;
static{
    Scanner scan = new Scanner(System.in);
    B = scan.nextInt();
    scan.nextLine();
    H = scan.nextInt();

    if(B < 0 || H < 0){
        flag = false;
        System.out.println("Java.lang.Exception: Breadth and height must be positive");
    } 
}
0
Vid