Javaのfinalは、「変更を禁止する」意味を持つキーワードです。具体的に説明すると、以下大きく3つの使い道があります。
- 変数: 値の再代入を禁止する。
- メソッド: サブクラスでのオーバーライドを禁止する。
- クラス: クラスの継承を禁止する。
その役割はいずれも「意図しない再定義や改変から保護すること」に集約されます。可読性や保守性、安全性を高めるために非常に重要な役割を担うキーワードです。このページでは、①~③のそれぞれのfinalキーワードの使い方を1からわかりやすく解説します。
Java:final変数
final変数とは、一度値を代入したら二度と変更できない変数です。Javaでは「定数」に近い挙動を示しますが、完全に同じではありません。C++など他言語のconstと混同しがちですが、Javaのfinalは「再代入を禁止する」という意味を主に持ちます。
public class FinalVariableExample {
public static void main(String[] args) {
final int number = 10;
System.out.println(number);
// number = 20; // コンパイルエラーとなる
}
}
// numberに再代入しようとするとコンパイルエラーが発生します。
final変数:初期化のタイミング
final変数は「一度しか値を代入できないので」宣言時またはコンストラクタ内、あるいはインスタンス初期化ブロック内で初期化されることが一般的です。
何度も説明しますが、一度しか値を代入できないためそのタイミングを意識してコードを書く必要があります。
class Person {
final String name;
Person(String name) {
this.name = name; // コンストラクタで初期化
}
void display() {
System.out.println("名前: " + name);
}
}
public class Main {
public static void main(String[] args) {
Person p = new Person("太郎");
p.display(); // "名前: 太郎"
// p.name = "次郎"; // コンパイルエラー
}
}
finalとイミュータブル
finalというキーワードを付与しても、その変数が参照するオブジェクト自体が不変(イミュータブル)になるわけではありません。finalは「変数への再代入を禁じる」だけであり、参照先のオブジェクトが持つフィールドの値を変更することまでは禁止しません。
class MyData {
int value;
}
public class ReferenceExample {
public static void main(String[] args) {
final MyData data = new MyData();
data.value = 100; // OK:参照先のフィールド書き換え
// data = new MyData(); // コンパイルエラー:再代入禁止
}
}
この例では、data.valueには自由にアクセスでき、値を変更することが可能です。しかし、dataそのものを新たなインスタンスで置き換えることはできません。本当の意味でオブジェクトを変更不可にするには、クラスをイミュータブルに設計する必要があります。
静的フィールドとstatic final
クラス全体で共有する定数値を定義するときには、static finalを用います。たとえば、数学的定数の円周率を表すPIなどは、多くのクラスやメソッドで共通して使われるため定数として定義するのが一般的です。
class Constants {
static final double PI = 3.14159;
}
public class Main {
public static void main(String[] args) {
System.out.println(Constants.PI); // 3.14159
// Constants.PI = 3.14; // コンパイルエラー:再代入不可
}
}
ローカル変数へのfinal適用と“effectively final”
Java 8以降では、匿名クラスやラムダ式内から参照される変数が事実上再代入されていない(“effectively final”)場合、finalと同等に扱われます。明示的にfinalを付けなくても、再代入されていない変数はラムダ式の中で参照可能です。
しかし、コードの可読性や意図を明確にするために、必要に応じてfinalを付ける場合もあります。
Java:finalメソッド
続いては、メソッドにfinalキーワードを付与した場合の挙動について説明します。結論、メソッドにfinalを付与するとオーバーライドを禁じることになります。
オーバーライドを禁止する
クラスを継承した場合、通常は親クラスのメソッドをサブクラスでオーバーライド(参考 オーバーライドとは?)することができます。しかし、メソッドにfinalが付いているとオーバーライドが禁止されます。
特に「メソッドの実装を絶対に変えたくない」「継承先でも動作を固定したい」といった場合にfinalを使用します。
class Parent {
final void show() {
System.out.println("親クラスのメソッド");
}
}
class Child extends Parent {
// void show() { } // コンパイルエラー
}
public class Main {
public static void main(String[] args) {
Child c = new Child();
c.show(); // 親クラスのメソッド
}
}
メソッドの安定性・保守性向上
finalメソッドを宣言することで、サブクラスが意図せず処理を変えてしまうリスクを防げます。APIとして公開されているクラスの中には、動作の一貫性を保つために、重要なメソッドをfinalとするケースがあります。
Java:finalクラス
最後に、クラスにfinalを付与した場合について。クラスの場合は、finalを付与することで「継承」を禁じる動きになります。
継承を禁止する
finalがクラスに付与されると、そのクラスを継承することができなくなります(参考 クラスの継承とは?)。代表例としてはJava標準ライブラリのStringクラスが有名です。
このようにクラス自体を継承不可にするのは、サブクラスによる動作変更を防ぎたい、もしくは継承することにメリットがないと判断されている場合などが挙げられます。
final class Animal {
void sound() {
System.out.println("動物の音");
}
}
// class Dog extends Animal {} // コンパイルエラー
使用頻度と注意点
クラスをfinalにするというのは、拡張性を意図的に削ぐことを意味します。ライブラリ作者がAPIの動作を固定化したい場合などには有効ですが、一般的な業務システム開発においては、クラス設計段階で「将来的に拡張の可能性はないか」を慎重に検討してから決定すべきです。
代表例:Stringクラス
JavaのStringクラスはfinalで宣言されているため、サブクラスでStringを拡張することはできません。これは、安全性・不変性(イミュータブル)が極めて重要であることに起因します。Stringがもし継承可能であれば、サブクラスで内部の文字列を変更される恐れが生じます。セキュリティリスクや言語仕様の整合性を保つために、final化が必須とされているのです。
finalキーワードの内部的な仕組み
コンパイル時のチェック
Javaコンパイラはfinalが付いた変数やメソッド、クラスに対して、コンパイル時にチェックを行います。具体的には、変数に対して再代入しようとしていないか、サブクラスでオーバーライドを行おうとしていないか、クラスを継承しようとしていないかを確認し、問題があればコンパイルエラーを出力します。
JITコンパイラと最適化
finalは、JIT(Just-In-Time)コンパイラによる最適化にも貢献すると言われることがあります。
例えば、finalで宣言された定数はコード中でリテラルとしてインライン化される可能性があります。ただし、実際のパフォーマンス向上は状況により異なるため、過度に「最適化のため」としてfinalを多用するのはおすすめできません。基本的には設計上の意図(変更不可にしたい)を明確化するために使うべきです。
finalとJavaのメモリモデル
finalフィールドの可視性保証
Javaのメモリモデルでは、finalフィールドは以下のような特性を持ちます。
これは「安全な公開(Safe Publication)」とも呼ばれ、マルチスレッド環境での動作保証に関係します。finalフィールドを使うことで、読み手は「コンストラクタ実行後、この値は常に正しい状態で一貫している」と判断できるのです。
スレッドセーフティとの関係
マルチスレッドプログラミングでしばしば問題となるのは、あるスレッドがオブジェクトを構築途中で他のスレッドに見せてしまうケースです。finalフィールドは「コンストラクタで一度値がセットされたら変更がない」という点で、その後の可視性が規定されています。
ただし、finalの利用だけで万能にスレッドセーフになるわけではありません。オブジェクトの状態が変化する場合は、当然ながら適切な同期や排他制御が必要です。

