その理由は、サブクラス化を許可し、サブクラスがオーバーライドできるメソッドに制限を設けない場合、その動作を保持する基本クラスを更新すると、サブクラスが壊れる可能性があるためです。言い換えれば、(特定のメソッドをオーバーライド可能として指定し、他のすべてのメソッドを作成することによって) 拡張するように特別に設計されていないクラスから継承することは安全ではありません。これは脆弱なfinal
基本クラスの問題と呼ばれます。この問題のより徹底的な分析については、このホワイト ペーパーを参照してください。
基本クラスが継承用に設計されておらず、公開 API (おそらくライブラリ) の一部である場合、深刻な問題が発生します。誰がコードをサブクラス化しているかを制御することはできません。基本クラスを見るだけでは、変更がサブクラスにとって安全かどうかを知る方法はありません。肝心なのは、無制限の継承を設計するか、継承を完全に禁止するかのどちらかです。
有限数のサブクラスを持つことが可能であることに注意してください。これは、プライベート コンストラクターと内部クラスを使用して実現できます。
class Base {
private Base() {}
(public|private) static final class SubA extends Base { ... }
(public|private) static final class SubB extends Base { ... }
(public|private) static final class SubC extends Base { ... }
}
内部クラスはプライベート コンストラクターにアクセスできますが、最上位クラスはアクセスできないため、内部からのみサブクラス化できます。これにより、「Base 型の値は SubA OR SubB OR SubC になり得る」という概念を表現できます。Base は通常空であり (実際には Base から何も継承していない)、すべてのサブクラスが制御下にあるため、ここでは危険はありません。
サブクラスの型を認識しているので、これはコードのにおいだと思うかもしれません。ただし、抽象化を実装するこの代替方法は、インターフェイスを補完するものであることを理解する必要があります。サブクラスの数が限られている場合、新しい関数を追加する (固定数のサブクラスを処理する) のは簡単ですが、新しい型を追加するのは困難です (新しいサブクラスを処理するには、既存のすべてのメソッドを更新する必要があります)。インターフェイスを使用するか、無制限のサブクラス化を許可すると、新しい型を追加する (固定数のメソッドを実装する) のは簡単ですが、新しいメソッドを追加するのは困難です (すべてのクラスを更新する必要があります)。一方の強みは他方の弱みです。このトピックの詳細については、「データの抽象化を理解する、再訪」を参照してください。編集:私の間違い; 私がリンクした論文は、異なる二重性 (ADT とオブジェクト/インターフェース) について語っています。私が実際に考えていたのはExpression Problemです。
継承を回避するそれほど深刻ではない理由もあります。クラス A から始めて、サブクラス B にいくつかの新しい機能を実装し、後でサブクラス C に別の機能を追加するとします。その後、両方の機能が必要であることに気付きますが、B と C の両方を拡張するサブクラス BC を作成することはできません。継承を考慮して設計し、壊れやすい基底クラスの問題を回避したとしても、これに噛まれる可能性があります。上で示したパターン以外では、継承のほとんどの使用は構成に置き換えたほうがよいでしょう。たとえば、ストラテジー パターンを使用します (または、言語でサポートされている場合は単に高階関数を使用します)。