1. 初期化ブロックとは
Java には2種類の初期化ブロックがあります。
- 非 static(通常の)初期化ブロック — 新しいオブジェクトを生成するたびに実行され、フィールドの初期化直後、コンストラクタの呼び出しより前に走ります.
- static 初期化ブロック — クラスがメモリにロードされたときに1回だけ実行されます(最初のオブジェクト生成や static フィールド/メソッドへのアクセスより前)。
インスタンス初期化ブロック
class 本体にそのまま記述します。static のようなキーワードは不要です。
public class User {
private String name;
// インスタンス初期化ブロック
{
System.out.println("インスタンス初期化ブロックを実行中!");
name = "デフォルト名";
}
public User() {
System.out.println("コンストラクタを実行中!");
}
}
static 初期化ブロック
static キーワードを付けて宣言します。
public class Config {
public static String appName;
static {
System.out.println("static 初期化ブロックを実行中!");
appName = "マイ・スーパーアプリ";
}
}
2. 初期化の順序: 何が先に実行されるのか
Java におけるクラス要素の初期化順序は行き当たりばったりではなく、厳密に決まっています。IKEA の家具を説明書なしで組み立てようとしたことがあるなら、順序が重要な理由が分かるはずです。手順を取り違えると、タンスではなくアート作品ができあがります。
初期化の順序:
- static フィールドと static ブロック — クラス内での宣言順。クラスのロード時に1回だけ実行される。
- インスタンス(非 static)フィールドとインスタンス初期化ブロック — 宣言順。各オブジェクト生成のたびに実行される。
- コンストラクタ — すべてのインスタンス初期化の後に実行される。
図解
+-------------------------------+
| JVM へのクラス読み込み |
+-------------------------------+
| 1. static フィールド |
| 2. static ブロック |
| ↓ |
| オブジェクトの生成 |
| ↓ |
| 3. インスタンスフィールド |
| 4. インスタンス初期化ブロック |
| 5. コンストラクタ |
+-------------------------------+
出力付きの例
どの順序で実行されるかを表示するクラスを書いてみましょう。
public class Demo {
static String staticField = print("1. static フィールド");
static {
print("2. static ブロック");
}
String field = print("3. インスタンスフィールド");
{
print("4. インスタンス初期化ブロック");
}
public Demo() {
print("5. コンストラクタ");
}
static String print(String msg) {
System.out.println(msg);
return msg;
}
public static void main(String[] args) {
System.out.println("最初の Demo オブジェクトを作成します:");
Demo d1 = new Demo();
System.out.println("\n2つ目の Demo オブジェクトを作成します:");
Demo d2 = new Demo();
}
}
実行結果
1. static フィールド
2. static ブロック
最初の Demo オブジェクトを作成します:
3. インスタンスフィールド
4. インスタンス初期化ブロック
5. コンストラクタ
2つ目の Demo オブジェクトを作成します:
3. インスタンスフィールド
4. インスタンス初期化ブロック
5. コンストラクタ
注意: static な部分(static)は最初にクラスへアクセスしたときの一度だけ実行されます。static でないものはオブジェクトを生成するたびに実行されます。
3. コード例: 初期化ブロックは何のためにあるか
コンストラクタだけでは足りないとき
複数のコンストラクタがあり、共通の初期化をそれぞれに重複させたくない場合、インスタンス初期化ブロックにまとめるのが便利です。
public class Person {
private String id;
private String name;
{
// このコードはどのコンストラクタよりも先に実行される
id = java.util.UUID.randomUUID().toString();
System.out.println("一意の id を生成します: " + id);
}
public Person() {
System.out.println("Person() 引数なし");
}
public Person(String name) {
this.name = name;
System.out.println("Person(String name)");
}
}
結果: どのオブジェクト Person を生成しても、まず id が生成され、その後に該当するコンストラクタが実行されます。
複雑な static データの初期化
static ブロックは、例えばファイルからの設定読み込み、コレクションの組み立て、データベース接続など、重い/複雑な static フィールドの初期化によく使われます。
public class Settings {
public static final java.util.Map<String, String> DEFAULTS;
static {
DEFAULTS = new java.util.HashMap<>();
DEFAULTS.put("theme", "light");
DEFAULTS.put("language", "ru");
System.out.println("Settings の static ブロック: 既定設定");
}
}
4. 役立つニュアンス
初期化ブロックを使うべきとき
使うと良いケース
- すべてのコンストラクタで必要となる共通初期化。
- 単純な代入では表せない複雑な static データ。
- static リソースの初期化(例: アプリ起動時に設定ファイルを読む)。
使わないほうが良いケース
- 通常の代入やコンストラクタで足りるなら、それらを使う。
- ビジネスロジックを初期化ブロックに隠さない。可読性と保守性が下がる。
- 初期化がコンストラクタ引数に依存するなら、コンストラクタで行う。
初期化ブロックの乱用に注意
初期化ブロックは強力ですが、常に必要なわけではありません。多くの場合は単純な代入やコンストラクタで十分です。ブロックが多すぎるクラスは読みにくく、保守が難しくなります。
コンストラクタ引数に依存する処理をインスタンス初期化ブロックに書かない
インスタンス初期化ブロックはコンストラクタより先に実行されるため、コンストラクタの引数は使えません。引数に基づく初期化が必要なら、コンストラクタ内で行いましょう。
static ブロックと継承
static 初期化ブロックは継承されません。各クラスが自分自身の static ブロックを持ちます。サブクラスがロードされると、まず基底クラスの static ブロックが実行され、次にサブクラスの static ブロックが実行されます。
5. 初期化ブロックでよくあるミス
エラー1: インスタンス初期化ブロックでコンストラクタ引数が見えると期待してしまう。
初心者がやりがちな誤りです。インスタンス初期化ブロックではコンパイルエラーになったり想定外の結果になります。インスタンス初期化ブロックはコンストラクタより前に実行されるため、引数はまだ存在しません。
エラー2: 初期化ブロックにロジックを詰め込みすぎる.
初期化ブロックが複雑になるとコードは分かりにくくなります。主要な処理はコンストラクタや別メソッドに分離しましょう。
エラー3: 複数の static ブロックの順序を誤解する。
クラスに複数の static ブロックがある場合、static フィールドとともにコード中の宣言順で実行されます。一方のブロックが他方の結果に依存していると、思わぬ挙動になることがあります。
エラー4: オブジェクト生成のたびに static ブロックが実行されると勘違いする。
static ブロックはクラスのロード時に一度だけ実行されます。再初期化を期待しても行われません。
エラー5: static ブロックからインスタンスフィールドにアクセスしようとする。
static ブロックで利用できるのは static な変数とメソッドだけです。インスタンス(通常の)フィールドへアクセスしようとするとコンパイルエラーになります。
GO TO FULL VERSION