抽象クラスとインターフェース
抽象クラスもインターフェースも、書式を理解しても、実際のプログラミングではどのように使い分けたらよいのかはっきりしません。
どのような場合に抽象クラス、そしてどういう時にはインターフェースを使ってプログラミングすれば良いのか、使い分けを考えてみましょう。
抽象クラス
フィールド
コンストラクタ
メソッド
抽象メソッド
}
※抽象クラスを継承した抽象クラスは、元の抽象クラスを拡張し、新しい抽象メソッドを追加したり、既存の抽象メソッドをオーバーライドして実装することができます。
※abstractは継承を前提としているため、final修飾子を同時に指定すると、コンパイル・エラーなります。
抽象メソッドの書式
抽象クラスを継承するクラス定義の書式
フィールド
コンストラクタ
メソッド
抽象メソッド のオーバライ
}
クラスと抽象クラスの使い分け
クラスはもともと(狭義の)オブジェクトを抽象化して作ります。では抽象クラスは一般的なクラスと何が違うのでしょう。
本題に入る前に「抽象」という言葉を正しく理解しましょう。
「抽象」というと抽象画の印象や、「具象」の反対語だから「曖昧な」といった意味だろうと思っている人もいます。ですが英語のabstractには「曖昧な」という意味はありません。英語の辞書を見ると、動詞として「~を取り除く」「~を概念化する」「~要約する」とあります。
概念化とは「いくつかの事物を一般化してとらえる考え方」ですから、抽象化とは「個別的な物を取り除いて、事物に共通なものを抜き出し、要約する」ことです。
クラスは個々のオブジェクトから個別的な物を取り除き、共通する点を抜き出して、具体的なフィールドとメソッドに要約してスーパークラスとし、各オブジェクト固有の事物をサブクラスで定義します。
一方、抽象クラスはこの概念化(抽象化)を一歩進めて、個別的な物が持つ振舞いの詳細は違っていても共通して持っている振舞いを、詳細を決めずに概念として定義する抽象メソッドを使って定義します。そして、振舞いの詳細はサブクラスに定義させます。
言い換えると、スーパークラスはサブクラスに対し振舞いの定義を強制しません。スーパークラスの振舞いをオーバーライドするかしないかはサブクラスの自由意志です。一方抽象クラスは抽象クラスを実装するサブクラスに対し、詳細は決めずに(詳細を自分では決められない場合を含め)決めなければいけない振舞いを規定して、その詳細を定義することを強制します。つまり、サブクラスが抽象メソッドをオーバーライドしないとコンパイルエラーになります。
また、Javaの場合クラスは一つのスーパークラスしか持てない(単一継承)ので、抽象クラスも単一継承です。
インターフェース
インターフェースはクラスとは別物の、継承構造を持ったクラス群に設定される、備えるべき性質、特性です。
インターフェース自体はインターフェースを継承(多重継承が可能)出来ますが、クラスはインターフェースを継承出来ず、実装(implements)して実現します。
実装は継承と異なり、クラスは複数のインターフェースを実装(多重実装)する事が出来ます。
実体(具体的な値)のないインターフェースは抽象クラスと同じようにインスタンス化することは出来ません。
なお、実装によって設定されるインターフェースですが、インタフェース内の抽象メソッドの実現は、実装したクラスが行わなければいけないわけではありません。インターフェースは特定のクラスではなく、クラスの継承構造全体に設定されたものなので、クラスの継承構造のどこかのクラスに具象メソッドがあれば良いのです。
定数フィールド
抽象メソッド
}
定数フィールドの書式(暗黙にpublic static final)
抽象メソッドの書式(暗黙にpublic abstract)
インターフェス実装の書式
メソッドのオーバーライド定義({ }だけを記述して処理は記述しない)
}
インターフェースは抽象クラスと違って相手に与える定数(相手に変えてもらっては困る)とstaticな初期化された変数(インスタンスを作らなくても使える)及び相手に解放する振舞いとしての抽象メソッドしか記述できません。
インタフェース内で宣言されるフィールドは暗黙的にpublic static finalで修飾された定数となります。従ってprotectedやprivateで修飾するとコンパイルエラーになります。また、初期化しない場合もコンパイルエラーになります。
インタフェースは具象クラスに実装してもらって初めて存在意義が出ます。そこで、インタフェースを実現したクラスの具象メソッドのアクセス修飾子はインターフェースと同じかより緩い物でなければいけません。インターフェースは暗黙的にpublicなのでpublicしか使えません。
interface InterfaceSample { final String teisu = "定数"; static int intNum = 0; void interfaceFeldPrint(); }
インターフェースを実装した抽象クラス(但し、実際は何もしていない)
abstract class InterfaceSubClass implements InterfaceSample{ }
抽象クラスを継承したサブクラス(インターフェースの実装を実際に行っている)
class AbstractSubClass extends InterfaceSubClass{ public void interfaceFeldPrint() { System.out.println("interfaceFeld : " + teisu + ", " + intNum); } }
抽象メソッドのサブクラスを通して、インターフェースで定義されたメソッドを実行する処理
public class InterfaceAbstractTest { public static void main(String[] args) { AbstractSubClass asc =new AbstractSubClass( ); asc.interfaceFeldPrint(); } }
実行結果
>java InterfaceAbstractTest interfaceFeld : 定数, 0 >
抽象クラスとインターフェースの使い分け
一般のクラスとは違うといっても抽象クラスもクラスですから、objectからのクラスの階層構造のどこかに位置します。一方、インターフェースは外見的に抽象クラスと似ていてもクラスではないので、この階層構造の中には位置しません。インターフェースは特定のクラスに付随するだけです。このためクラスの継承(extends)に対し、インターフェースでは実装(implements)と言います。
抽象クラスといえどもクラスです。抽象メソッドだけでなく具象メソッドも持つことが出来ますから、抽象メソッドもスーパークラスの性格を引き継いだサブクラスを作る仕組み、という点ではクラスと変わりません。
インターフェースは英語の意味から考えるとインターフェースは「つなぎ合わせる」「調和させる」です。つまり、インターフェースはクラス間をつなぎ合わせる仕組みです。この点こそが抽象クラスとインターフェースの違いであり、使い分けのポイントです。では何をつなぎ合わせ、調和させるのでしょう。
実際の開発では大勢のプログラマがプログラミングを分担し、数多くの機能(振舞い)を開発するので、全ての開発が同期して開発できるわけではありません。つまりある部分を開発し、テストしようとしても関連する別の部分はまだ設計中で結合してテストすることが出来なかったり、開発している場所が離れていてリアルタイムで開発を同期させることが出来なかったり、そもそもシステムが複雑で部分部分を個別にテストしてからでないと結合してテストが出来なかったりします。
このような場合、インターフェースを使えば複雑なシステムをモジュールに分解して、モジュール単位で開発を進め、単体テストが終わったら実装モジュールを実際の物に入れ替えて総合テストを行う事ができます。つまりインターフェースは実装の仕方は問わずに相手にしてもらいたい事だけを定義し、自分と相手モジュールをつなぎ合わせます。