Java道|finalの使い方と役割

自由に広げるだけが、よい設計ではありません。
final を使うと、変えてはいけない技、広げてはいけない型、書き換えてはいけない値を、Javaのコードにはっきり刻めます。

これまで、Javaのオブジェクト指向では、継承によってクラスを広げたり、オーバーライドによって親クラスのメソッドを子クラス向けに作り直したりできることを見てきました。

継承やオーバーライドは、とても便利です。
親クラスに共通部分をまとめ、子クラスで個性を加えられるからです。

鬼滅の刃風にたとえると、鬼殺隊士という共通の土台があり、そこから柱のような特別な隊士を作るイメージです。
さらに、柱ごとに戦い方や表示の仕方を変えたい場合は、オーバーライドで自分流に作り直せます。

ただし、設計では「自由に変えられること」だけが正解ではありません。

中には、次のようなものもあります。

守りたいもの
子クラスで勝手に変えてほしくない処理隊士認証の手順
これ以上継承させたくない完成済みのクラス最終奥義を持つ完成形の隊士クラス
書き換えられてはいけない値最大隊士数、任務規則、固定された基準値

このような「ここは変えないでほしい」という設計上の意図を表すために使うのが final です。

final は、単に厳しく制限するためのものではありません。
変えてよい場所と、変えてはいけない場所をはっきり分けるための修飾子 です。

final は「それ以上変更させない」を表す

final の基本イメージは、とてもシンプルです。

それ以上変更させない

これが final の中心にある意味です。

ただし、final をどこに付けるかによって、何を変更できなくするのかが変わります。

final を付ける場所変更できなくなるもの意味
メソッドオーバーライド子クラスで上書きできない
クラス継承サブクラスを作れない
フィールド値の再代入値を変更できない

鬼滅の刃風にたとえると、次のように考えると分かりやすいです。

final の対象鬼滅の刃風のイメージ
final メソッドこの技の型は変えてはいけない
final クラスこの隊士の型は完成形なので派生させない
final フィールドこの数値は隊の絶対ルールなので書き換えない

同じ final でも、付ける場所によって役割が変わります。
そのため、final を見たときは、まず「何を固定しているのか」を確認することが大切です。

↓クリックすると拡大表示されます。

メソッドに final を付ける意味

まずは、メソッドに付ける final です。

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

たとえば、DemonSlayer クラスに showRule() というメソッドがあるとします。

class DemonSlayer
{
    public final void showRule()
    {
        System.out.println("鬼殺隊士は任務規則を守ります。");
    }
}

このように public final void showRule() と書くと、サブクラスで同じ showRule() を定義して上書きすることはできません。

class PillarSlayer extends DemonSlayer
{
    // これはできない
    public void showRule()
    {
        System.out.println("柱専用の規則に変えます。");
    }
}

このように書こうとすると、エラーになります。

なぜなら、親クラス側で final を付けているため、showRule() は「子クラスで変更してはいけないメソッド」として固定されているからです。

なぜメソッドをオーバーライド禁止にするのか

オーバーライドは便利です。
子クラスごとにふさわしい動きへ作り直せるからです。

しかし、すべてのメソッドを自由にオーバーライドできると困る場合があります。

たとえば、次のような処理です。

変えられると困る処理理由
認証処理勝手に変えられると安全性が崩れる
共通ルール表示子クラスごとに違う内容になると統一性がなくなる
重要な手順処理順序が変わると不具合の原因になる
基本契約の処理クラス全体の意味が崩れる

鬼滅の刃風にたとえると、鬼殺隊士には全員が守るべき共通の任務規則があります。

たとえば、

共通規則内容
身元確認隊士であることを確認する
任務確認担当任務を確認する
規則遵守鬼殺隊の決まりを守る

このような処理を子クラス側で勝手に変えられると、隊全体のルールが崩れてしまいます。

そのため、親クラスのメソッドに final を付けて、
この振る舞いは共通ルールとして固定します
と宣言します。

メソッド final の見方

メソッドに final が付いているときは、次のように読み取ります。

書き方読み取り方
public final void showRule()このメソッドは子クラスでオーバーライドできない
final がないメソッド条件を満たせばオーバーライドできる

つまり、メソッド final は、子クラスの自由をすべて否定するものではありません。
変えてよいメソッドと、変えてはいけないメソッドを分けるための指定 です。

メソッドの種類final を付けるか
子クラスごとに動きを変えてよいもの付けない
全クラスで同じ動きを守らせたいもの付ける

鬼滅の刃風にたとえると、隊士ごとの呼吸の型は変えてよくても、隊の絶対規則は変えてはいけない、ということです。

クラスに final を付ける意味

次に、クラスに付ける final です。

クラスに final を付けると、そのクラスは継承できなくなります。
つまり、そのクラスを親クラスにしてサブクラスを作ることができません。

たとえば、次のように書きます。

final class FinalBreathingStyle
{
    public void showStyle()
    {
        System.out.println("完成された呼吸の型です。");
    }
}

この FinalBreathingStyle クラスは final class なので、次のように継承できません。

// これはできない
class CustomBreathingStyle extends FinalBreathingStyle
{
}

final class は、
このクラスはここで完成形なので、これ以上派生させません
という意味を持ちます。

なぜクラスを継承禁止にするのか

継承は強力です。
既存のクラスを土台にして、新しいクラスを作れるからです。

しかし、すべてのクラスを自由に継承できるようにしておくと、設計が崩れることがあります。

たとえば、次のようなクラスです。

継承させたくないクラス理由
完成済みのクラスこれ以上機能を変えられたくない
内部ルールが厳密なクラス派生によって安全性が崩れる可能性がある
使い方を固定したいクラス想定外のサブクラスを防ぎたい
不変性を守りたいクラス子クラスで状態管理を変えられたくない

鬼滅の刃風にたとえると、ある呼吸の最終奥義が「これ以上変化させてはいけない完成形」として定められているようなものです。

もし、その型を自由に派生できるようにしてしまうと、元の意味やルールが崩れるかもしれません。

そのため、クラスに final を付けて、

この型はここで完成です
これ以上、派生形は作らせません

と宣言します。

クラス final の役割を整理する

クラスに final を付ける場面を整理すると、次のようになります。

こんなときfinal を付ける意味
そのクラスを完成形として扱いたい継承を禁止する
派生されると設計が崩れるサブクラス作成を防ぐ
使い方を厳密に固定したい型の広がりを止める
想定外のオーバーライドを防ぎたい子クラスを作らせない

継承は拡張性を生みます。
一方で、final class は拡張性よりも安定性を優先します。

考え方意味
継承を許す将来の拡張を考える
final class にする完成形として固定する

鬼滅の刃風にたとえると、まだ成長や派生を許す隊士の型もあれば、完成された奥義として固定する型もある、ということです。

フィールドに final を付ける意味

最後に、フィールドに付ける final です。

フィールドに final を付けると、その値は一度設定したあと変更できなくなります。
これは、途中で書き換えてはいけない値を表すときに使います。

たとえば、鬼殺隊の最大柱数を固定したい場合、次のように書けます。

static final int MAX_PILLAR_COUNT = 9;

この MAX_PILLAR_COUNT は final なので、あとから別の値を代入できません。

// これはできない
MAX_PILLAR_COUNT = 10;

final フィールドは、この値は固定です という意味をコードに表します。

フィールド final はなぜ大切か

プログラムの中には、変わってよい値と、変わってはいけない値があります。

鬼滅の刃風にたとえると、隊士の現在地や体力は状況によって変わります。
しかし、鬼殺隊の基本規則や最大柱数のような値は、勝手に変わってはいけません。

値の種類変わるか
隊士の現在地変わる
隊士の体力変わる
任務中の状態変わる
最大柱数変わらない
基本規則の番号変わらない

もし、変わってはいけない値が途中で書き換えられると、次のような問題が起こります。

問題内容
前提が崩れるルールが途中で変わってしまう
処理結果が不安定になる同じ処理でも結果が変わる可能性がある
バグの原因になるどこで値が変わったのか追いにくい
設計意図が伝わりにくい固定値なのか状態値なのか分からない

final を付けておけば、この値は変更しないものだと明確になります。

フィールド final は定数につながる

final フィールドは、定数としてよく使われます。

特に、次の形はよく出てきます。

static final int MAX_PILLAR_COUNT = 9;

ここでは static と final を組み合わせています。

修飾子役割
staticクラス全体で共有する
final値を変更できなくする

つまり static final は、
クラス全体で共通して使う、変更できない値
を表します。

鬼滅の刃風にたとえると、特定の隊士だけが持つ値ではなく、鬼殺隊全体で共有する決まりの数です。

たとえば、次のような定数が考えられます。

static final int MAX_PILLAR_COUNT = 9;
static final int DEFAULT_MISSION_LEVEL = 1;
static final String ORGANIZATION_NAME = "鬼殺隊";

このような値は、宣言時に初期化しておくのが基本です。
final はあとから値を変えられないため、最初に何の値にするかを決めておく必要があります。

final とオーバーライドの関係

final は、オーバーライドと深く関係します。

オーバーライドは、親クラスのメソッドを子クラスで自分向けに作り直す仕組みでした。
これはとても便利です。

しかし、すべてのメソッドを自由に作り直せると、親クラスが守りたい共通ルールまで変えられてしまう可能性があります。

そこで、設計では次のように使い分けます。

メソッドの種類どうするか
子クラスごとに変えてよいもの通常どおりオーバーライド可能にする
親クラスのルールとして固定したいものfinal を付ける

鬼滅の刃風にたとえると、隊士ごとの戦い方は違ってよいです。
水の呼吸、炎の呼吸、風の呼吸のように、隊士ごとに個性があります。

一方で、隊士認証や任務規則の確認のような処理は、勝手に変えられると困ります。

つまり final は、
オーバーライドを否定するものではなく、オーバーライドを許す場所と許さない場所を分けるための道具
です。

final と継承の関係

クラスに final を付けると、継承できなくなります。

これは、継承が悪いからではありません。
継承させないこと自体が、設計として正しい場合があるからです。

継承を許すと、次のようなメリットがあります。

継承を許すメリット内容
クラスを拡張できる新しいサブクラスを作れる
共通部分を再利用できる親クラスの機能を使える
多態性につなげられるスーパークラス型でまとめて扱える

一方で、継承を許すと次のようなリスクもあります。

継承を許すリスク内容
想定外のサブクラスが作られる設計者が想定していない使い方をされる
ルールが崩れる子クラスで不自然な実装がされる可能性がある
管理が複雑になる派生クラスが増えすぎる
一貫性が失われるクラス本来の意味が薄れる

final class は、この広がりを意図的に止めるための仕組みです。

鬼滅の刃風にたとえると、
この呼吸の型は完成形なので、これ以上派生させない
と決めるようなものです。

final と値の制御の関係

フィールドに final を付けると、値を変更できなくなります。

これは、オブジェクト指向における状態管理とも関係します。

クラスのフィールドには、状態を表すものがあります。
その中には、変わるべき値と、変わってはいけない値があります。

鬼滅の刃風に考えると、次のようになります。

フィールド変わるか理由
hp変わる戦闘で増減する
currentArea変わる任務で移動する
rank変わる場合がある昇格する可能性がある
MAX_PILLAR_COUNT変わらない鬼殺隊の決まりとして固定
ORGANIZATION_NAME変わらない組織名として固定

このように、変わってよいものと、変わってはいけないものを分けると、コードの意味がはっきりします。

final フィールドは、
この値は状態ではなくルールです
と伝えるための表現でもあります。

final をどう読み取ればよいか

final が出てきたら、まず「何が止められているのか」を見ます。

final の対象止めているもの読み取り方
メソッドオーバーライドこの振る舞いは固定
クラス継承この型は完成形
フィールド値の変更この値は固定

たとえば、次のコードを見たとします。

public final void confirmRule()

これは、confirmRule() が子クラスでオーバーライドできないという意味です。

次のコードならどうでしょうか。

final class FinalBreathingStyle

これは、FinalBreathingStyle を継承できないという意味です。

次のコードなら、値が変更できません。

static final int MAX_PILLAR_COUNT = 9;

このように、final は1つの単語ですが、見る場所によって意味が変わります。

Math クラスのような final の活用イメージ

final の考え方は、数学やルールを扱うクラスを考えると理解しやすくなります。

たとえば、計算ルールを扱うクラスでは、勝手に継承されて動きを変えられると困ることがあります。
また、円周率のような値は途中で書き換えられてはいけません。

こうした場面では、final の考え方がとても自然です。

鬼滅の刃風にたとえると、これは個々の隊士が勝手に変えてよい技ではなく、世界の法則や鬼殺隊全体の規則に近いものです。

固定したいものfinal の使い方
勝手に変えてほしくない計算処理final メソッド
継承させたくない計算クラスfinal クラス
書き換えてはいけない値final フィールド

final は、柔軟性を減らすためだけに使うものではありません。
変えてはいけないものを明確に守るために使う と考えると、役割が分かりやすくなります。

図:final の3つの使い方

↓クリックすると拡大表示されます。

この図が示していること

この図では、final の3つの使い方を並べて整理しています。

左側は final メソッドです。
メソッドに final を付けると、子クラスでオーバーライドできなくなります。

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

右側は final フィールドです。
フィールドに final を付けると、その値を変更できなくなります。

この図から分かることは、final の共通イメージは「それ以上変更させない」ですが、何を止めるのかは付ける場所によって変わるということです。

図:final と継承・オーバーライドの境界線

↓クリックすると拡大表示されます。

この図が示していること

この図では、final が継承やオーバーライドの中でどのように境界線を作るかを表しています。

DemonSlayer クラスには、attack() と final showRule() があります。

attack() には final が付いていないので、PillarSlayer クラスでオーバーライドできます。
一方、showRule() には final が付いているので、PillarSlayer クラスでオーバーライドできません。

また、static final int MAX_RULE_LEVEL は値が固定されているため、あとから変更できません。

この図から分かることは、final が「何も広げさせないためのもの」ではなく、
自由に変えてよい部分と、守るべき固定部分を分けるためのもの
だということです。

final を鬼滅の刃風に整理する

鬼滅の刃風にたとえると、final は「絶対に破ってはいけない決まり」をコードにしたものです。

final の使い方鬼滅の刃風のイメージ
final メソッドこの技の型は変えるな
final クラスこの隊士の型は完成形なので派生させるな
final フィールドこの数値は隊の決まりだから変えるな

オブジェクト指向では、自由に拡張できることは大きな強みです。
でも、本当に読みやすく安全な設計では、自由に変えてよい部分と、変えてはいけない部分が分かれています。

final は、その「ここは固定する」という線を引くための修飾子です。

この内容で押さえておきたいポイント

ポイント内容
final の基本意味それ以上変更させない
final メソッドオーバーライドできない
final クラス継承できない
final フィールド値を再代入できない
static finalクラス全体で共有する定数によく使う
final とオーバーライド変えてよいメソッドと変えてはいけないメソッドを分ける
final と継承派生を許すクラスと完成形のクラスを分ける
final と値状態として変わる値と、ルールとして固定する値を分ける

final を見たときは、「これは何を固定しているのか」と考えると理解しやすくなります。

メソッドならオーバーライドを止めています。
クラスなら継承を止めています。
フィールドなら値の変更を止めています。

この感覚がつかめると、final は単なる制限ではなく、設計を安定させるための大切な道具として見えてきます。