うさぎ組

ソフトウェア開発、チームによる製品開発、アジャイル、ソフトウェアテスト

クラスやメソッドでのfinalの使い方

クラスやメソッドでfinalをうまく使うためにはどうすればいいかを調べてみました。
個人的には「引数にはfinal乱発したい派」な感じです。


まず、クラスやメソッドでfinalと宣言するとどんなことになるかっていうのをおさらいすると

メソッドをfinalと宣言すると、拡張したクラスではそのメソッドをそのメソッドの振る舞いを変更するために、そのメソッドをオーバーライドできません。言い換えれば、そのメソッドの最終(final)バージョンだということです。クラス全体を次のようにfinalと宣言することもできます。

final class NoExtending{
// ...
}

finalと宣言されたクラスを、他のクラスが拡張できませんし、その結果finalクラスのすべてのメソッドは実質的にfinalとなります。


プログラミング言語Java 第4版 3.6 メソッドとクラスをfinalにする P.83


メソッドをfinal宣言すればメソッドのロジックは守られ、クラスをfinal宣言すればクラスの契約は守られます。
誰からも振る舞いを改変される恐れがなく、その機能を安心して使えるようになります。
ただ、このとき注意する点があって、

もしメソッドがfinalならば、そのメソッドの実装を信頼できます。(ただし、そのメソッドがfinalではないメソッドを呼び出していなければですが)。


プログラミング言語Java 第4版 3.6 メソッドとクラスをfinalにする P.83

というようにfinalなメソッドの中で呼び出されるfinalではないメソッドに十分に注意しなければいけない点です。これはコンストラクタの中で呼んでいるメソッドについても同様。


とは言っても、final宣言をすることは機能の拡張を制限していることを示しています。
この柔軟性が欠けてしまう点についてはKent Beckも実装パターンの中で次のように述べています。

このメソッドで使用される不変条件がかなり複雑で微妙なのであれば、これぐらいの自己防衛は正当化されるかもしれない。しかし、そのオブジェクトを誤って壊す人は誰もいないという保障を得るのと引きかえに、役立つ方法で誰かがオーバーライドする可能性は失われることになる。またそのかわり、別の方法でやってもらえたはずの作業を自分がやらなければならなくなる。私自身はfinalを使うことはない。メソッドをオーバーライドをする正当な理由があるのに、そのメソッドがファイナルになっていて、苛々させられることも時々ある。


実装パターン 8章 メソッド メソッドの可視性 P.101

確かに、サブクラスが作れないことで煮え湯を飲まされるようなときはあります。
継承元のクラスからうまくインターフェースを抽出できればいいのですが、それがうまくいかないときはやはりサブクラスを作りたくなります。


ここまで読むとfinalにすることは大抵は悪いのかって思うのですが、そういうわけではない。な。と思ったのが、Effective Javaの次の部分です。

クラスはオーバーライド可能なメソッドの自己利用を文書化しなければなりません。
Effective Java 第2版 第4章 項目17継承のために設計および文書化する、でなければ継承を禁止する P.86
ここまでで、継承のためにクラスを設計することは、そのクラスにかなりの制限を課すことが明らかです。
〜略〜
通常の具象クラスについてはどうでしょうか。伝統的に、それらはfinalでもないし、サブクラス化のための設計や文書化がなされていませんが、そのような状態は危険です。
〜略〜
この問題に対する最善の解決策は、安全にサブクラス化されるための設計と文書化されていないクラスでのサブクラス化の禁止です。サブクラス化を禁止するには、2つの方法があります。2つの中で容易な方法は、クラスをfinalと宣言することです。もう1つの方法は、すべてのコンストラクタをprivateがパッケージプライベートにして、コンストラクタの代替としてpublicのstaticファクトリーメソッドを追加することです。


Effective Java 第2版 第4章 項目17継承のために設計および文書化する、でなければ継承を禁止する P.90

finalが悪いというよりは、継承を考慮したクラス設計ができないのであれば、finalにするかファクトリーメソッドを用意して、サブクラス化は禁止したほうがいい。と。


また、レガシーコード改善ガイドでは次のように述べられています。

まじめな話として、sealedやfinalは判断を間違ったものであり、プログラム言語に決して加えるべきではなかったと考えることは簡単です。しかし、本当の間違いは我々にあります。制御不可能なライブラリに直接依存することで、自ら困難を招いているだけにすぎません。
いつの日か、主流のプログラミング言語が、テストのために特別な権限を用意してくれるかもしれません。しかしそれまでの間、sealedやfinalのようなキーワードは控えめに使うことを推奨します。そしてライブラリに含まれているsealedやfinalを指定したクラスが必要な場合は将来の変更に対応できるようにラッパーで隔離するとよいでしょう。


レガシーコード改善ガイド 第10章 このメソッドをテストハーネスで動かすことができません 10.2 言語の「便利な」機能 P.159


全体を通してみると、クラスやメソッド宣言でfinalは乱発するものではなく、
絶対に安全にしておきたいクラスやメソッド
もしくは十分に継承を考慮できていないクラスやメソッドに対する解決策の1つとして使う程度がよい。
ということのようです。

つまり、final宣言しない以上は、final宣言が必要ないようなクラス設計にしましょう。ということですかね。