Java | 基礎文法:final

Java Java
スポンサーリンク

final の全体像

final は「再代入や変更を禁止する」ためのキーワードです。対象は変数・フィールド・メソッド・クラスで、付ける場所によって意味が変わります。基本は「変数なら再代入不可」「メソッドならオーバーライド不可」「クラスなら継承不可」。参照型に付けた final は「参照の付け替え禁止」であり「中身の変更禁止」ではない点が重要です。


変数・フィールドの final

ローカル変数と再代入禁止

void calc() {
    final int TAX_RATE = 10;
    // TAX_RATE = 8; // コンパイルエラー(再代入は禁止)
    int priceWithTax = 100 * (100 + TAX_RATE) / 100;
    System.out.println(priceWithTax);
}
Java

メソッド内の final 変数は一度値を決めたら再代入できません。意図せぬ書き換えを防ぎ、読み手に「固定値」であることを伝えます。

参照型の final と不変性

final StringBuilder sb = new StringBuilder("A");
sb.append("B");  // OK(参照先の中身は可変なら変えられる)
// sb = new StringBuilder("C"); // NG(参照の付け替えは不可)
Java

final は「参照を別オブジェクトへ付け替えられない」だけです。本当に不変にしたいなら不変型(String, List.of(…) など)を使います。

blank final とコンストラクタ初期化

class User {
    private final String name; // blank final(初期化は後で)

    User(String name) {
        this.name = name; // すべてのコンストラクタで必ず初期化する
    }
}
Java

フィールドを宣言時に初期化しない「blank final」は、全コンストラクタで必ず一度だけ初期化する必要があります。生成後は再代入不可です。

effectively final とラムダ・内部クラス

void demo() {
    int base = 10; // 以後書き換えないなら「実質 final」
    java.util.function.IntUnaryOperator f = x -> x + base; // 参照可
    // base++; // 実質 final ではなくなるためコンパイルエラー
}
Java

ラムダや匿名クラスから参照できるローカル変数は「実質 final」(以後変更しない)である必要があります。キャプチャする値の安定性を保証するためです。


メソッド・クラスの final

final メソッド(オーバーライド禁止)

class Base {
    public final void log(String msg) {
        System.out.println(msg);
    }
}
class Sub extends Base {
    // @Override
    // public void log(String msg) { ... } // NG(final はオーバーライド不可)
}
Java

派生クラスで変更されたくない挙動を守ります。セキュリティ・一貫性が重要なメソッドに適用します。

final クラス(継承禁止)

public final class Id {
    private final int value;
    public Id(int value) { this.value = value; }
}
Java

設計上、拡張による不整合を避けたいときや、完全に不変な値オブジェクトとして使いたいときに有効です。

final 引数(パラメータ)

void print(final String msg) {
    // msg = msg.trim(); // NG(再代入不可)
    System.out.println(msg);
}
Java

メソッド引数の再代入を防ぎます。可読性程度の利点で、必須ではありません。副作用を抑えたいチーム規約で使われます。


定数設計と final の使いどころ

static final とコンパイル時定数

public final class Config {
    public static final int PORT = 8080;             // リテラルなら埋め込み(定数式)
    public static final String HOST = "localhost";
}
Java

「プリミティブや String で、static final、右辺が定数式」の場合はコンパイル時定数になり、他コードに値が埋め込まれます。外部公開の値を頻繁に変える設計は避け、設定ファイルなどへ逃がすのが無難です。

不変オブジェクトと防御的コピー

public final class Texts {
    public static final java.util.List<String> MENU = java.util.List.of("Home", "Products");
    // 可変の配列を公開するならコピーを返す
    private static final String[] COLORS = {"RED", "BLUE"};
    public static String[] colors() { return COLORS.clone(); } // 防御的コピー
}
Java

final の参照であっても中身が可変だと外部から書き換えられます。公開時は不変型を選ぶか、防御的コピーで守ります。


並行性と final の可視性保証(重要ポイントの深掘り)

final フィールドは「コンストラクタで初期化され、コンストラクタが正常に完了した後」であれば、他スレッドから安全に初期化済みとして見えます。同期なしでも「初期化の可視性」が保証されるため、スレッドセーフな不変オブジェクトの要にできます。

final class Point {
    private final int x, y;
    Point(int x, int y) { this.x = x; this.y = y; } // 完了後、他スレッドから安全に読み取れる
    int x() { return x; }
    int y() { return y; }
}
Java

ただし「コンストラクタ逸脱」(this をコンストラクタ中に外へ渡す)をすると保証が崩れます。初期化が終わる前に公開しないことが重要です。


よくある誤解と注意点

final と finally / finalize の違い

  • final は「変更禁止」のキーワード。
  • finally は例外構文の「必ず実行される」節。
  • finalize はオブジェクト回収時のフックで非推奨。混同しないようにしましょう。

final は「高速化の魔法」ではない

(理屈上)最適化に寄与する場面はありますが、現代のJIT最適化は十分賢いため「意味と安全性のために使う」が基本方針です。


例題で身につける

例 1: 不変値オブジェクト

public final class Money {
    private final java.math.BigDecimal amount;
    private final String currency;

    public Money(String amount, String currency) {
        this.amount = new java.math.BigDecimal(amount); // 不変型
        this.currency = currency;
    }

    public java.math.BigDecimal amount() { return amount; }
    public String currency() { return currency; }

    public Money add(Money other) {
        if (!currency.equals(other.currency)) throw new IllegalArgumentException();
        return new Money(amount.add(other.amount).toPlainString(), currency);
    }
}
Java

例 2: ラムダで実質 final を活かす

public class CaptureDemo {
    public static void main(String[] args) {
        int base = 10; // 以後変更しない
        var f = (java.util.function.IntUnaryOperator) x -> x + base;
        System.out.println(f.applyAsInt(5)); // 15
        // base++; // 実質 final でなくなるためコンパイルエラー
    }
}
Java

例 3: final メソッドで契約を守る

class Audit {
    public final void record(String msg) {
        System.out.println("[AUDIT] " + msg);
    }
}
class CustomAudit extends Audit {
    // record のオーバーライドは禁止。ログ形式を固定できる。
}
Java

仕上げのアドバイス(重要部分のまとめ)

final は「再代入や拡張を止めて、意味を固定する」ための道具です。値はローカル final、共有値は static final、オブジェクトの不変性は「不変型+final フィールド」で設計する。ラムダが参照する変数は実質 final に保ち、公開時は不変オブジェクトや防御的コピーで中身を守る。並行性では final フィールドの可視性保証を活かし、コンストラクタ中の this 公開を避ける。final を「意図の宣言」として使えば、コードは自然に安全で読みやすくなります。

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