クラスを最終的または封印されたものとしてマークしたいのはなぜですか?
3 に答える
ウィキペディアによると、「封印されたクラスは、主に派生を防ぐために使用されます。これらは、コンパイル時に別のレベルの厳密さを追加し、メモリ使用量を改善し、実行時の効率を向上させる特定の最適化をトリガーします。」
また、Patrick Smacchia のブログから:
バージョニング: クラスが最初に封印されている場合、将来、互換性を損なうことなく非封印に変更できます。(…)
パフォーマンス: (…) JIT コンパイラーがシール型を使用した仮想メソッドの呼び出しを検出した場合、JIT コンパイラーはメソッドを非仮想的に呼び出すことにより、より効率的なコードを生成できます。(…)
セキュリティと予測可能性: クラスは自身の状態を保護し、自身が破損することを許さない必要があります。クラスがアンシールされると、フィールドを内部的に操作するデータ フィールドまたはメソッドがアクセス可能でプライベートでない場合、派生クラスは基本クラスの状態にアクセスして操作できます。(…)
これらはすべてかなりの理由です-私は実際に今調べたまで、パフォーマンス上の利点の影響に気づいていませんでした:)
バージョニングとセキュリティ ポイントは、コードの信頼性という点で大きな利点のように思えます。これは、あらゆる種類の大規模プロジェクトで十分に正当化されます。もちろん、単体テストのためのドロップインではありませんが、役に立ちます。
継承用の型を作成することは、ほとんどの人が考えるよりもはるかに難しい作業だからです。デフォルトですべての型をこのようにマークするのが最善です。これにより、他の型が拡張される予定のない型から継承されるのを防ぐことができます。
型を拡張する必要があるかどうかは、それを作成した開発者の決定であり、後でやって来てそれを拡張したい開発者ではありません。
Joshua Bloch は、彼の著書「Effective Java」でそれについて語っています。彼は「継承を文書化するか、それを許可しない」と言います。ポイントは、クラスは作者とクライアントの間の契約のようなものだということです。クライアントが基本クラスから継承できるようにすると、この契約はより厳密になります。それを継承する場合は、いくつかのメソッドをオーバーライドする可能性が高くなります。それ以外の場合は、継承を構成に置き換えることができます。どのメソッドをオーバーライドできるか、およびそれらを実装するために何をする必要があるか - ドキュメント化する必要があります。そうしないと、コードが予測できない結果を招く可能性があります。私が覚えている限り、彼はそのような例を示しています-これはメソッドを持つコレクションクラスです
public interface Collection<E> extends Iterable<E> {
...
boolean add(E e);
boolean addAll(Collection<? extends E> c);
...
}
いくつかの実装、つまり ArrayList があります。ここで、それを継承していくつかのメソッドをオーバーライドしたいので、要素が追加されたときにコンソールにメッセージを出力します。では、 addとaddAllの両方をオーバーライドする必要がありますか、それともaddのみをオーバーライドする必要がありますか? addAllがどのように実装されているかによって異なります。内部状態を直接処理するか (ArrayList のように)、またはadd を呼び出すか(AbstractCollection のように) によって異なります。または、 addとaddAllの両方によって呼び出されるaddInternalがあるかもしれません. このクラスから継承することを決定するまで、そのような質問はありませんでした。あなたがそれを使うだけなら - それはあなたを悩ませません。したがって、クラスの作成者は、クラスから継承することを望む場合、それを文書化する必要があります。
また、将来的に実装を変更したい場合はどうすればよいでしょうか? 彼のクラスが使用されるだけで、継承されていない場合、実装をより効率的なものに変更することを妨げるものは何もありません。そのクラスから継承し、ソースを見て、addAllがaddを呼び出していることがわかった場合は、 addのみをオーバーライドします。後で作成者が実装を変更したため、 addAllが addAllを呼び出さなくなりました。プログラムが壊れており、addAllが呼び出されたときにメッセージが出力されません。または、ソースを見て、addAllが add を呼び出さないことがわかったので、addとaddAllをオーバーライドします。現在、作成者は実装を変更しているため、addAllが呼び出されますadd - addAllが呼び出されると、プログラムが再び壊れます。要素ごとにメッセージが 2 回出力されます。
したがって、クラスを継承したい場合は、その方法を文書化する必要があります。将来、一部のサブクラスを壊す可能性のある何かを変更する必要があると思われる場合は、それを回避する方法を考える必要があります。クライアントがクラスから継承できるようにすることで、クライアントにクラスを使用させるだけの場合よりも多くの内部実装の詳細を公開できます。将来のバージョンで変更されることが多い内部ワークフローを公開します。
一部の詳細を公開し、クライアントがそれらに依存している場合、それらを変更することはできなくなります。あなたに問題がない場合、または上書きできるものとできないものを文書化した場合、それで問題ありません。時々、あなたはそれを望んでいません。「内部実装の詳細を自由に変更したいので、このクラスを使用するだけで、決して継承しないでください」と言いたい場合があります。
したがって、基本的に「クラスは子供を持ちたくないので、その希望を尊重する必要があるため」というコメントは正しいです。
そのため、実装の詳細の変更が継承よりも価値があると考えている場合、誰かがクラスを final/sealed としてマークしたいと考えています。継承と同様の結果を得る方法は他にもあります。