抽象クラスとは
インターフェースは状態を持たずに振る舞いを宣言します。通常のクラスは完全に実装されており、インスタンス化できます。抽象クラスはその中間に位置します。通常のクラスと同じようにフィールド、コンストラクタ、実装済みのメソッドを持てますが、一部のメソッドを未実装のまま残すこともでき、直接インスタンス化することは禁止されています。これはabstractキーワードで印を付けます。
考え方としては、サブクラスに共通するものすべてを1か所にまとめつつ、本当に異なる部分は各サブクラスに埋めさせる、ということです。
AnimalはすべてのサブクラスのためにgetName()を一度だけ定義し、sound()をabstractとして宣言します。これはシグネチャはあるが本体がないメソッドで、Dogが用意しなければなりません。
抽象クラスはインスタンス化できない
抽象クラスには未完成のメソッドがある可能性があるため、直接生成すると不完全なオブジェクトが残ってしまいます。コンパイラはこれを拒否します。
Animal a = new Animal("???"); // error: Animal is abstract; cannot be instantiated
常に具象サブクラス、つまりすべての抽象メソッドを実装したサブクラスをインスタンス化します。そのサブクラスのインスタンスは抽象型の変数に保持でき、これがまさに抽象化の使い方です。
抽象メソッドはサブクラスに決定を強制する
abstractメソッドは、サブクラスが守らなければならない約束です。サブクラスがそのうちの1つの実装を忘れると、そのサブクラス自身が抽象になり、コンパイラがそれを知らせてくれます。これが抽象クラスの最大の利点です。特定の振る舞いが存在することを保証しつつ、それが何をするかは指定しないのです。
describe()はShapeの中で一度だけ書かれているのに、各サブクラス独自のarea()を呼び出します。抽象クラスが共通の足場を提供し、サブクラスが具体的な内容を提供します。
共有状態とコンストラクタ
従来のインターフェースとは異なり、抽象クラスはインスタンスフィールドを保持し、コンストラクタを定義できます。コンストラクタが単独でAnimalやShapeを作ることは決してありません。サブクラスが生成されるときにsuper(...)を通じて実行され、共有状態を初期化します。
balanceフィールド、deposit、applyInterestは1か所にまとまっています。実際に変わる方針であるinterestRate()だけが抽象のまま残されます。サブクラスのコンストラクタは、この継承された状態を初期化するためにsuper(...)を呼び出さなければなりません。
落とし穴: abstractとfinalの併用
abstractとfinalは正反対です。abstractメソッドはオーバーライドを要求し、finalメソッドはそれを禁止します。同じものを両方の方法で印付けすること、あるいは抽象クラスをfinalにすることはコンパイルエラーです。また、抽象クラスは抽象メソッドがゼロでもよいことを覚えておきましょう。インスタンス化を防ぐためだけにクラスをabstractと宣言することは正当であり、継承だけを意図した基底型にとって役立つことがあります。
abstract final class Bad { } // error: abstract and final conflict
abstract class Base {
abstract final void f(); // error: an abstract method can't be final
}
抽象クラスとインターフェース
両者は重なる部分があるため、選択は何を共有したいかに帰着します。
- 抽象クラス - 状態とコードを共有する密接に関連したクラスに使います。
SavingsもCheckingもAccountを継承し、balanceフィールドとdepositのロジックを受け継ぎます。クラスが継承できるのは1つだけです。 - インターフェース - 無関係なクラスが共有できる能力に使います。
BirdとAirplaneは、実装をいっさい共有せずに両方ともFlyableになれます。クラスは複数を実装できます。
よくあるパターンは両者を組み合わせます。インターフェースが契約を定義し、抽象クラスが定型コードを実装することで、具象サブクラスは固有の部分だけを埋めればよくなります。
次: ポリモーフィズム
上記のどの例でも、サブクラスのインスタンスを抽象型の変数に保持し、メソッドを呼び出すと自動的にサブクラスの振る舞いが得られたことに注目してください。この1つの能力 — 1つの参照型で実行時に多くの振る舞いをとること — がポリモーフィズムであり、抽象クラスとインターフェースが真価を発揮する理由です。それが次のページのテーマです。
よくある質問
Javaの抽象クラスとは何ですか?
抽象クラスとは、abstractキーワードを付けて宣言され、それ自体ではインスタンス化できないクラスです。継承されることを前提としています。完全に実装されたメソッドやフィールド(サブクラスのための共有状態とコード)と、本体を持たないabstractメソッドを混在させることができ、後者の実装は各サブクラスの責任になります。
Javaで抽象クラスをインスタンス化できますか?
いいえ。new AbstractType()はコンパイルエラーになります。抽象クラスには未実装の(abstract)メソッドがある可能性があり、その場合オブジェクトが不完全になるためです。すべての抽象メソッドを実装した具象サブクラスをインスタンス化し、それを抽象型の変数に格納します。
Javaにおける抽象クラスとインターフェースの違いは何ですか?
抽象クラスはインスタンスフィールド、コンストラクタ、部分的に実装されたロジックを持てますが、クラスが継承できるのは1つだけです。インターフェースはインスタンス状態を持たずに振る舞いを宣言し、クラスは複数を実装できます。密接に関連するサブクラス間で状態とコードを共有したいときは抽象クラスを、無関係なクラスに共通の能力を与えたいときはインターフェースを使います。