この記事では次のように述べています。
不変であるためクラスをfinalにすることは、そうする正当な理由です。
私はこれに少し困惑しています...スレッドセーフとシンプルさのPOVから不変性は良いことだと理解していますが、これらの懸念は拡張性とはやや直交しているようです。では、不変性がクラスを final にする正当な理由になるのはなぜでしょうか?
これについての説明は、書籍「Effective Java」に記載されています。
Java のクラスを検討BigDecimal
してください。BigInteger
BigInteger
不変クラスは、作成時に事実上 final でなければならないことが広く理解されていなかったBigDecimal
ため、それらのメソッドはすべてオーバーライドされる可能性があります。残念ながら、後方互換性を維持しながら、これを事後に修正することはできませんでした。
信頼されていないクライアントからの BigInteger または BigDecimal 引数の不変性にセキュリティが依存するクラスを作成する場合、その引数が、信頼されていないサブクラスのインスタンスではなく、「実際の」 BigInteger または BigDecimal であることを確認する必要があります。 . 後者の場合は、変更可能である可能性があるという前提で防御的にコピーする必要があります。
public static BigInteger safeInstance(BigInteger val) {
if (val.getClass() != BigInteger.class)
return new BigInteger(val.toByteArray());
return val;
}
サブクラス化を許可すると、不変オブジェクトの「純度」が損なわれる可能性があります。
Liskov Substitution Principle に従って、サブクラスは拡張できますが、親のコントラクトを再定義することはできません。基本クラスが不変の場合、契約を破ることなくその機能を便利に拡張できる例を見つけるのは困難です。
原則として、不変クラスを拡張して基本フィールドを変更することは可能であることに注意してください。たとえば、基本クラスに配列への参照が含まれている場合、配列内の要素を final と宣言することはできません。明らかに、メソッドのセマンティクスはオーバーライドによって変更することもできます。
すべてのフィールドを private として宣言し、すべてのメソッドを final として宣言できると思いますが、継承の用途は何でしょうか?
クラスが final の場合、それを拡張して変更可能にすることはできないためです。
フィールドを final にしても、それは参照を再割り当てできないことを意味するだけで、参照されるオブジェクトを変更できないという意味ではありません。
拡張する必要がある不変クラスの設計での使用はあまり見られないため、 final は不変性をそのまま維持するのに役立ちます。
主にセキュリティだと思います。String が final であるのと同じ理由で、セキュリティ関連のコードが不変として扱いたいものはすべて final でなければなりません。
不変であると定義されたクラスがあるとします。これを MyUrlClass と呼びますが、final とはマークしません。
さて、だれかが次のようなセキュリティ マネージャ コードを書きたくなるかもしれません。
void checkUrl(MyUrlClass testurl) throws SecurityException {
if (illegalDomains.contains(testurl.getDomain())) throw new SecurityException();
}
DoRequest(MyUrlClass url) メソッドに入れる内容は次のとおりです。
securitymanager.checkUrl(urltoconnect);
Socket sckt = opensocket(urltoconnect);
sendrequest(sckt);
getresponse(sckt);
しかし、MyUrlClass を final にしなかったため、これを行うことはできません。彼らがそれを実行できない理由は、もし実行した場合、getDomain() をオーバーライドして、最初に呼び出されたときに「www.google.com」を返し、「www.evilhackers.org」を返すようにするだけで、コードがセキュリティ マネージャーの制限を回避できるからです。 2 つ目は、そのクラスのオブジェクトを DoRequest() に渡します。
ところで、evilhackers.org が存在するとしても、私は何も反対しません...
セキュリティ上の懸念がない場合は、プログラミング エラーを回避することがすべてであり、もちろんそれをどのように行うかはあなた次第です。サブクラスは親のコントラクトを保持する必要があり、不変性はコントラクトの一部にすぎません。しかし、クラスのインスタンスが不変であると想定されている場合、それを final にすることは、それらが実際にすべて不変であることを確認するための 1 つの良い方法です (つまり、サブクラスの可変インスタンスが存在しないことを確認します)。クラスが求められます)。
特に、継承用に不変クラスを設計する肯定的な理由がある場合は、参照した記事を「すべての不変クラスは最終でなければならない」という指示と見なすべきではないと思います。それが言っていたのは、不変性を保護することが最終的な正当な理由であり、架空のパフォーマンスの問題(その時点で実際に話していること)が有効ではないということです。同様に正当な理由として、「継承用に設計されていない複雑なクラス」を与えたことに注意してください。不変クラスの継承を説明しないのと同じように、複雑なクラスで継承を説明しないことは避けるべきものであるとかなり主張することができます。しかし、それを説明できない場合でも、少なくとも防止することで、この事実を知らせることができます。
パフォーマンス上の理由からも、クラスを不変にすることをお勧めします。たとえば、Integer.valueOf を取り上げます。この静的メソッドを呼び出すときに、新しい Integer インスタンスを返す必要はありません。前回そのインスタンスへの参照を渡したときに変更しなかったという知識の中で、以前に作成されたインスタンスを安全に返すことができます (これは、セキュリティ上の理由からも適切な理由だと思います)。
私は、クラスを拡張可能に設計するか、拡張不可能にするべきであるという、Effective Java でのこれらの問題に関する見解に同意します。何かを拡張可能にする意図がある場合は、おそらくインターフェイスまたは抽象クラスを検討してください。
また、クラスを final にする必要はありません。コンストラクターを非公開にすることができます。