final フィールドとは何か
final フィールドは「一度だけ代入でき、その後は再代入できない」フィールドです。オブジェクトの生成時に値を確定し、以降は参照を変えないことで、設計の予測可能性が上がります。値オブジェクトや設定スナップショットなど“変わってはいけない前提”を持つ型で特に有効です。
何のために使うのか(重要ポイントの深掘り)
不変条件を守るために使います。生成時に検証して確定すれば、後から書き換えられず整合性が維持されます。並行実行でも「途中で値が変わらない」ため、共有が安全になり、同期やロックの負担を減らせます。equals/hashCode と組み合わせると、ハッシュ構造(HashMap/HashSet)で安全にキーとして使えます。
初期化のルールと“空の final”(blank final)
final は必ず「コンストラクタ内」または「フィールド宣言時」、あるいは「インスタンス初期化子」で一度だけ代入します。宣言時に代入しない final は“空の final”で、全コンストラクタで必ず代入される必要があります。
public final class Money {
private final int amount; // 空の final(宣言時未代入)
private final String currency; // 空の final
public Money(int amount, String currency) {
if (amount < 0) throw new IllegalArgumentException("amount>=0");
if (currency == null || currency.isBlank()) throw new IllegalArgumentException("currency");
this.amount = amount; // コンストラクタで一度だけ代入
this.currency = currency.trim(); // ここで確定
}
public int amount() { return amount; }
public String currency() { return currency; }
}
Java宣言時に代入する方法もあります。設定値や定数など、常に同じ値を持つならこちらが簡潔です。
public final class ConfigDefaults {
private final int timeoutMs = 3000; // 宣言時に確定
public int timeoutMs() { return timeoutMs; }
}
Java参照は固定でも“中身”は別(重要な注意点)
final は「参照の再代入禁止」であり、「参照先の中身が不変」を保証するものではありません。可変オブジェクトを final にすると、参照は固定でも内部状態は変更できてしまいます。不変を保ちたいなら、内部も不変の型を使うか、防御的コピーで囲います。
public final class Tags {
private final java.util.List<String> list; // 参照は final
public Tags(java.util.List<String> input) {
var xs = (input == null ? java.util.List.<String>of() : input).stream()
.map(s -> s == null ? "" : s.trim())
.filter(s -> !s.isEmpty())
.toList();
this.list = java.util.List.copyOf(xs); // 変更不可ビューで不変を守る
}
public java.util.List<String> asList() { return list; } // 不変ビューのまま返す
}
Java配列は特に注意が必要です。配列自体の要素は変更できるため、final 配列をそのまま公開しないでください。返却時にコピーしましょう。
public final class Bytes {
private final byte[] data;
public Bytes(byte[] input) {
this.data = (input == null) ? new byte[0] : java.util.Arrays.copyOf(input, input.length);
}
public byte[] data() {
return java.util.Arrays.copyOf(data, data.length); // 防御的コピー
}
}
Javastatic final と“定数”設計
クラス全体で共有する変更不可の値は static final にします。定数は大文字スネークケース(MAX_SIZE など)で表記すると意図が伝わりやすく、JIT の最適化にも親和的です。
public final class MathConstants {
public static final double PI = 3.141592653589793;
public static final int DEFAULT_SCALE = 2;
}
Javaインスタンスごとの固定値なら、static は付けません。インスタンス生成時に確定する、という違いを意識してください。
並行性と“安全な公開”(重要ポイントの深掘り)
final フィールドは、コンストラクタで正しく初期化され、コンストラクタの完了後に他スレッドへ公開されれば、他スレッドは常に完全に初期化された値を見ます。可変状態よりも可視性の問題が起きにくく、安全な共有に向いています。並行性のバグを避けたいなら、できる限り final を使い、不変に寄せるのが王道です。
例題で身につける
例 1: 設定スナップショット(不変)
public final class AppConfig {
private final String region;
private final int timeoutMs;
public AppConfig(String region, int timeoutMs) {
if (region == null || region.isBlank()) throw new IllegalArgumentException("region");
if (timeoutMs <= 0) throw new IllegalArgumentException("timeout>0");
this.region = region.trim();
this.timeoutMs = timeoutMs;
}
public String region() { return region; }
public int timeoutMs() { return timeoutMs; }
public AppConfig withTimeout(int ms) { return new AppConfig(region, ms); } // 変更は新インスタンス
}
Javaこの形なら、生成後に状態が変わらないため、どこから参照されても安全です。
例 2: 値オブジェクト(等価・ハッシュの一貫性)
import java.util.Objects;
public final class ProductCode {
private final String value;
public ProductCode(String raw) {
var v = raw == null ? "" : raw.trim().toUpperCase();
if (v.isBlank()) throw new IllegalArgumentException("code required");
this.value = v;
}
@Override public boolean equals(Object o) {
return o instanceof ProductCode pc && value.equals(pc.value);
}
@Override public int hashCode() { return Objects.hash(value); }
@Override public String toString() { return value; }
}
Javafinal によって値が固定され、コレクションのキーとしても安全に扱えます。
例 3: ユーティリティの定数
public final class Urls {
public static final String HTTP = "http";
private Urls() {}
public static boolean isHttp(java.net.URI u) {
return u != null && HTTP.equalsIgnoreCase(u.getScheme());
}
}
Java定数と機能を混ぜず、意図が明確な API になります。
つまずきやすいポイントと回避
final にしても内部の可変オブジェクトは変わる、という罠に注意してください。コレクションや配列は防御的コピーで受け取り・返却を徹底します。空の final は全コンストラクタで必ず代入が必要です。1つでも代入漏れがあるとコンパイルエラーになります。テストや拡張で“差し替えたくなる”場合は、フィールドを final に保ちつつ、外部依存はインターフェース注入に切り替えると柔軟性を維持できます。
仕上げのアドバイス(重要部分のまとめ)
final フィールドは「生成時に値を確定し、以後変えない」ための道具です。不変条件をコンストラクタで保証し、参照を固定することで、整合性・並行性・読みやすさが大きく向上します。内部が可変になりがちな配列やリストは必ず防御的コピーを使い、定数は static final で明示します。まず“不変に寄せる”方針を徹底すれば、多くのバグの入口を塞げます。
