コードレビューで「このクラスにfinalを付けてください」と言われたことが、一度はあるのではないでしょうか。
単なる習慣に見えますが、理由を知るとコードの見方が変わります。
結論から言うと、finalは「このクラスを継承によって拡張するつもりはない」という宣言です。パフォーマンスと設計の安全性を同時に高める、意外に重みのある一行です。
今回は、Swiftのクラスにfinalを付ける本当の理由を整理します。
finalとは何ですか?
finalは継承とオーバーライドを禁止するキーワードです。
クラスの前に付けると、ほかのクラスがそのクラスを継承できなくなります。
メソッドやプロパティの前に付ければ、そのメンバーだけオーバーライドを禁止できます。
final class Logger {
func log(_ msg: String) { print("[LOG] \(msg)") }
}
let logger = Logger()
logger.log("支払い完了") // 出力: [LOG] 支払い完了
Loggerを継承するためにclass FileLogger: Loggerと書くと、コンパイルエラーになります。
「このクラスはここで終わりです」と釘を刺すわけです。
finalを付けるとなぜ速くなるのでしょうか?
一つ目の理由はパフォーマンスです。ポイントはディスパッチ方式にあります。
継承の可能性が残っていると、コンパイラーは実際に呼ばれるメソッドをランタイムまで判断できないため、毎回テーブルを調べて探す必要があります。
これを動的ディスパッチと呼びます。クラスごとのメソッドテーブル(vtable)を参照してから呼び出します。
一方、finalが付くと状況は変わります。
子クラスが割り込めないため、コンパイラーは「この呼び出しは必ずこのメソッドだ」と確信できます。
そのためテーブルを探さず、直接呼び出せます。これが静的ディスパッチです。
さらに、短いメソッドなら呼び出し箇所にコードを展開するインライン化も可能になります。
一回の呼び出しコストは大きくありませんが、ループ内で何千回も呼ばれるメソッドなら差が積み重なります。
本当の理由はパフォーマンスより「設計」です
実際、私はパフォーマンスよりこちらのほうが重要だと思います。
脆弱な基底クラス問題(fragile base class)を防げるからです。
継承を開いておくと、自分が作った親クラスの内部動作に誰でも自由に介入できます。
親のメソッドを何気なく一つ変更しただけで、それをオーバーライドした子クラスが次々に壊れることがあります。
継承はカプセル化を最も大きく壊す関係です。子クラスが親の内部まで見られるようになるからです。
そのため、オブジェクト指向設計の原則でもこう言われます。
継承のために設計し、ドキュメント化せよ。そうでなければ継承を禁止せよ。—Effective Javaの有名な助言です。
finalは、この助言をコードで実践するためのツールです。
「継承用に設計していないので、開いておかない」という意図を、コンパイラーが強制してくれます。
意図が明確になれば、後からコードを読む同僚も迷いにくくなります。
では、必ず付けるべきなのでしょうか?
ここで重要な話です。Swiftでは、参照型は必ずしもクラスである必要はありません。
状態を値として扱えるなら、structのほうが適している場合が多くあります。structにはそもそも継承がありません。
つまり「finalを付けるか」を考える前に、「これは本当にクラスである必要があるか」を先に問うべきです。
それでもクラスを使う必要があるなら、次の基準で判断してください。
| 状況 | 判断 |
|---|---|
| 継承を想定していないクラス | finalを付ける(デフォルト) |
| パフォーマンスが重要な繰り返し呼び出しコード | finalで静的ディスパッチを促す |
| 明確な拡張ポイントを設計したフレームワーク | finalを付けず開いておく |
| UIViewControllerなど継承を前提とするAPI | 付けない |
まとめると、次のように覚えると簡単です。
- 継承を意図していないなら、基本的にfinalを付ける
- 拡張ポイントは、開きたい場所だけを選んで開く
- structで済むなら、そもそもクラスを避ける
面接ではこう質問されます
Q. finalキーワードを付けると、どのようなメリットがありますか?
継承とオーバーライドを防ぎ、設計意図を明確に示せます。同時に、コンパイラーは動的ディスパッチの代わりに静的ディスパッチを使えるため、呼び出しコストが下がり、インライン化の最適化も可能になります。
Q. では、すべてのクラスにfinalを付けるべきですか?
継承のために設計し、ドキュメント化したクラスは開いておくべきです。ただし、その意図がないクラスは基本的に閉じておくほうが安全で、値として扱う意味が合うなら、まずstructを検討するのがよいでしょう。
finalの一行は単なる最適化のヒントではなく、「このクラスをどう使うのか」への答えです。
今日からクラスを新しく作るときは、「継承を開いておく理由はあるか」と一度問いかけてみてください。その習慣だけで、コードはずっと堅牢になります。

