CodeGym /コース /JAVA 25 SELF /static と final フィールドの初期化

static と final フィールドの初期化

JAVA 25 SELF
レベル 15 , レッスン 3
使用可能

1. 静的フィールド: どのように初期化するか?

静的フィールド(static)はオブジェクトに属するのではなく、クラス全体に属します。その値はそのクラスのすべてのオブジェクトで1つだけ共有されます。たとえるなら、全社員で共有する「共通の財布」のようなものです。誰が来ても財布はひとつだけです。

宣言時の初期化

最も簡単で一般的な初期化方法は、宣言時にそのまま値を代入するやり方です。

public class User {
    private static int userCount = 0; // ここで直接初期化

    // ... 残りのコード
}

このフィールドは、クラス User が最初にメモリへロードされたときに初期化されます。

静的ブロックでの初期化

ときには、より複雑な初期化ロジックが必要になることがあります。たとえば、ファイルからデータを読み込んだり計算を行ったりする場合です。このようなときは静的初期化ブロックを使います。

public class Config {
    public static String configPath;

    static {
        // このブロックはクラスのロード時に一度だけ実行される
        configPath = System.getenv("APP_CONFIG_PATH");
        if (configPath == null) {
            configPath = "/etc/app/default.conf";
        }
        System.out.println("Config path を初期化しました: " + configPath);
    }
}

静的ブロックはいつ実行されるのか?

  • クラスへ初めてアクセスしたとき(例: 最初のオブジェクトを生成する、または任意の静的メソッド/フィールドを呼び出すとき)。
  • 各クラスにつき一度だけ実行されます。

静的フィールドへのアクセスの注意点

  • 静的フィールドにはクラス名経由でアクセスします: User.userCount
  • オブジェクト経由でもアクセスできますが、読み手を混乱させるため悪いスタイルと見なされます。

例:

User u1 = new User();
User u2 = new User();
System.out.println(User.userCount); // 正しい
System.out.println(u1.userCount);   // 動くが非推奨!

2. final フィールド: いつ、どのように初期化するか

final は「このフィールドは一度だけ代入でき、その後は変更されない」ことを示す修飾子です。初期化後、フィールドの値は不変になります。インスタンスのフィールドであればそのオブジェクトの固定的な性質になり、静的フィールドであれば(クラスに属するなら)クラス共通の定数になります。

使用例:

  • 定数(例えば PI)。
  • 生成後に変更できないオブジェクトの一意識別子。

final フィールドの初期化要件

Java には厳格なルールがあります。final フィールドは、宣言時か、あるいはクラスのすべてのコンストラクタの中で必ず初期化しなければなりません

宣言時の初期化

public class Circle {
    public static final double PI = 3.1415926535; // クラス定数
    private final String id = "CIRCLE";           // オブジェクトの定数
}

コンストラクタでの初期化

final フィールドの値がオブジェクト生成時にしか分からない場合もあります。

public class User {
    private final int id;

    public User(int id) {
        this.id = id; // コンストラクタで final フィールドに代入
    }
}

重要: クラスに複数のコンストラクタがある場合、final フィールドはそれらすべてで初期化されていなければなりません!

組み合わせの例

public class Token {
    private final String value;
    private final long timestamp;

    public Token(String value) {
        this.value = value;
        this.timestamp = System.currentTimeMillis();
    }
}

誤った初期化によるコンパイルエラー

final フィールドの初期化を忘れると、コンパイラはプログラムをコンパイルさせてくれません。

public class Broken {
    private final int x; // 未初期化

    public Broken() {
        // x に代入していない!
    }
}
// エラー: variable x might not have been initialized

3. static と final の組み合わせ: クラス定数の宣言

public static final の組み合わせは非常によく使われます。Java では、プログラム実行中に変わらないクラス定数をこのように宣言します。これらの定数はクラス全体に属し、すべてのオブジェクトで同一です。

構文と例

public class MathUtils {
    public static final double PI = 3.1415926535;
    public static final String APP_NAME = "MyApp";
}

解説:

  • public — どこからでもアクセス可能。
  • static — 個々のオブジェクトではなくクラスに属する。
  • final — 初期化後に変更できない。

使用例

double area = MathUtils.PI * r * r;
System.out.println(MathUtils.APP_NAME);

慣例: 定数名は通常、PROPSHINAMI_BUKVAMI_S_PODCHERKIVANIEM のように大文字とアンダースコアで記述します。

4. コード例: 静的フィールドと final フィールドの初期化バリエーション

例 1: 定数を持つシンプルなクラス

public class Constants {
    public static final int DAYS_IN_WEEK = 7;
    public static final String COMPANY = "OOO Romashka";
}

例 2: 静的ブロックで初期化された静的フィールド

public class AppConfig {
    public static final String DEFAULT_PATH;

    static {
        // 複雑なロジックを実行できる
        String env = System.getenv("APP_PATH");
        if (env != null) {
            DEFAULT_PATH = env;
        } else {
            DEFAULT_PATH = "/usr/local/app";
        }
    }
}

例 3: オブジェクトの final フィールド、コンストラクタでの初期化

public class User {
    private static int nextId = 1;
    private final int id;
    private String name;

    public User(String name) {
        this.id = nextId++;
        this.name = name;
    }

    public int getId() { 
        return id; 
    }
    public String getName() {
        return name; 
    }
    public void setName(String name) { 
        this.name = name; 
    }
}

使用例:

User u1 = new User("Ivan");
User u2 = new User("Mariya");
System.out.println(u1.getId()); // 1
System.out.println(u2.getId()); // 2

例 4: final フィールドの誤った初期化によるエラー

public class BadExample {
    private final int number;

    public BadExample() {
        // number は初期化されていない!
    }
}
// コンパイルエラー: variable number might not have been initialized

5. 静的フィールドと final フィールドを扱う際のベストプラクティス

public static final は真の定数にだけ使う

将来値が変わりうるもの(例: 社員のリスト)には final を付けないでください。定数は本当に不変であるべきです。

public static final int MAX_USERS = 1000; // 良い
public static final String[] USERS = new String[100]; // 悪い!

参照である USERS 自体は変わりませんが、配列の中身は変更できます。これは思わぬバグにつながる可能性があります。

複雑な初期化には静的ブロックを使う

代入一発で表せない定数(計算が必要、ファイルからの読み込みが必要など)の場合は静的ブロックを使いましょう。

静的フィールドの乱用は避ける

静的フィールドはグローバル変数です。多用しすぎると、バグの追跡が難しくなり、保守性も低下します。

可変オブジェクトを public static final にしない

オブジェクトが可変である場合、それを public かつ final にしないでください。あらゆるコードから内部にアクセスできてしまい、いつか誰かが必ず変更します。

6. 静的フィールドと final フィールドの初期化でよくあるミス

エラー 1: final フィールドの未初期化。 final フィールドを宣言時またはすべてのコンストラクタで初期化し忘れると、コンパイラはエラーを出します。たとえば、コンストラクタが2つあるのに、そのうち1つで final フィールドへの代入を忘れた場合はエラーになります。

エラー 2: final フィールドの値を変更してしまう。 初期化後に final フィールドの値を変更しようとすると、コンパイルエラーになります。

public class Demo {
    private final int x = 5;
    public void change() {
        x = 10; // エラー: cannot assign a value to final variable x
    }
}

エラー 3: public な可変の static final フィールド。 可変オブジェクト(String[]int[] など)を public static final にすると、どのコードからでも中身を変更できてしまいます。これはカプセル化を壊し、発見が難しいバグを招きます。

エラー 4: 静的ブロックで非静的フィールドを使ってしまう。 静的ブロックでは非静的フィールドにアクセスできません。なぜなら、それらはまだ初期化されておらず(この段階ではそもそも存在しません)、参照できないからです。

エラー 5: 予期しない初期化順序。 静的ブロックの中で、コードの後方で宣言された静的フィールドにアクセスすると、それらがまだ初期化されていない可能性があります。静的フィールドを静的ブロックで使う場合は、必ず先に宣言しておきましょう。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION