Java 逆引き集 | final の使い方(変数・クラス・メソッド) - 不変性確保

Java Java
スポンサーリンク
スポンサーリンク

final の全体イメージ(「あとから変えない」と宣言するためのキーワード)

Java の final は、一言で言えば「これはあとから変えません」という宣言だ。 変数に付ければ「再代入禁止」、クラスに付ければ「継承禁止」、メソッドに付ければ「オーバーライド禁止」という意味になる。 不変性を確保することで、コードの予測可能性が上がり、バグの入り込む余地が減る——それが final の本質的な役割だ。

変数に付ける final(再代入を禁止して「値を固定する」)

変数に final を付けると、「その変数には一度しか値を代入できない」というルールになる。 ローカル変数でもフィールドでも同じだ。

public void example() {
    final int port = 8080;
    // port = 9090; // コンパイルエラー:再代入できない
}
Java

ここで重要なのは、「変数の参照先を変えられない」ということであって、「参照先の中身まで不変になるわけではない」という点だ。 例えば、final なリスト変数は「別のリストを代入し直すこと」はできないが、「リストに要素を追加すること」はできる。

final List<String> names = new ArrayList<>();
names.add("Taro");      // OK
// names = new ArrayList<>(); // NG:別のリストを代入できない
Java

この性質は、「この変数は一度決めたら別のものを指さない」という保証を与える。 特にコンストラクタで初期化するフィールドに final を付けると、「このオブジェクトのライフタイムを通じて、そのフィールドは必ず同じ参照を指す」という不変性が得られる。

public class User {
    private final String name;

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

    public String getName() {
        return name;
    }
}
Java

namefinal を付けることで、「User の名前は生成時に決まり、その後は変わらない」という設計意図がコードに刻まれる。 これは「オブジェクトの状態を予測しやすくする」という意味で非常に強力だ。

クラスに付ける final(継承を禁止して「設計を固定する」)

クラスに final を付けると、そのクラスは継承できなくなる。

public final class ImmutablePoint {
    private final int x;
    private final int y;

    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() { return x; }
    public int getY() { return y; }
}
Java

このクラスを継承しようとするとコンパイルエラーになる。

// NG:ImmutablePoint は final なので継承できない
public class ExtendedPoint extends ImmutablePoint {
    public ExtendedPoint(int x, int y) {
        super(x, y);
    }
}
Java

クラスを final にする意味は、「この型の振る舞いをサブクラスで変えられたくない」という設計上の意図を表現することだ。 例えば、完全に不変な値オブジェクト(座標、金額、日付など)を作りたいとき、final クラスにしておくと、サブクラスで状態を変えるようなメソッドを追加される危険を避けられる。

また、ライブラリ設計では「このクラスは拡張ポイントではない」「継承ではなくコンポジションで使ってほしい」というメッセージとして final が使われることも多い。 「継承を許すかどうか」は API の契約に関わるため、final はその契約を明示するためのキーワードだと言える。

メソッドに付ける final(オーバーライド禁止で「振る舞いを固定する」)

メソッドに final を付けると、そのメソッドはサブクラスからオーバーライドできなくなる。

public class BaseService {

    public final void execute() {
        validate();
        doExecute();
        log();
    }

    protected void validate() {
        // 共通の検証
    }

    protected void doExecute() {
        // サブクラスで具体処理を実装
    }

    protected void log() {
        // 共通のログ
    }
}
Java

ここで execute はテンプレートメソッドとして「処理の流れ」を固定し、 doExecute だけをサブクラスで差し替えられるようにしている。

public class UserService extends BaseService {

    @Override
    protected void doExecute() {
        // ユーザー処理の具体実装
    }
}
Java

executefinal を付けることで、「処理の流れそのものは変えさせない」という設計意図をコードで保証している。 もし execute がオーバーライド可能だと、サブクラス側で勝手に流れを変えられてしまい、 「validate → doExecute → log」という前提が崩れ、バグや仕様の破綻につながりやすくなる。

メソッドに final を付けるのは、「ここは拡張ポイントではない」「この振る舞いは固定したい」という意思表示であり、 継承を前提とした設計において、どこまで自由にしてどこから先は守ってほしいかを線引きするための道具だ。

不変性確保としての final(「変わらない」を増やすとコードは楽になる)

final の本質は、「変わらないものを増やす」ことにある。 変数が変わらない、フィールドの参照が変わらない、クラスの振る舞いが変わらない——そうした“不変”が増えるほど、コードは予測しやすくなり、テストしやすくなり、バグが入り込みにくくなる。

特に、次のような場面では final が効いてくる。

コンストラクタで初期化したフィールドに final を付けて、「生成後は変わらない状態」を作る ユーティリティクラスを final にして、「継承ではなく静的メソッドとして使う」ことを明示する テンプレートメソッドに final を付けて、「処理の流れ」を固定し、拡張ポイントを限定する

初心者のうちは「とりあえず動かす」ために可変なものを増やしがちだが、 少しずつ「ここは変わらない方が安全だ」「ここは変えさせたくない」という場所に final を付けていくと、 コードベース全体が「意図された不変性」を持つようになり、設計としての強度が上がっていく。

final は単なるキーワードではなく、「この値・この振る舞いは変えない」という設計上の約束をコンパイラに守らせるための宣言だ。 その宣言をどこに置くかを意識し始めたとき、コードは一気に“偶然動いているもの”から“意図して動くもの”へと変わっていく。

タイトルとURLをコピーしました