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(参照の付け替えは不可)
Javafinal は「参照を別オブジェクトへ付け替えられない」だけです。本当に不変にしたいなら不変型(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(); } // 防御的コピー
}
Javafinal の参照であっても中身が可変だと外部から書き換えられます。公開時は不変型を選ぶか、防御的コピーで守ります。
並行性と 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 を「意図の宣言」として使えば、コードは自然に安全で読みやすくなります。
