オブジェクト指向プログラミングにとって、抽象化はその大きな特徴の一つです。Javaでは、OOPの抽象化を2つの形式で表現できます:インターフェースと抽象クラス。この2つには似ている点が非常に多く、また異なる点も非常に多くあります。多くの人は初心者の頃、これらを自由に置き換えて使えると思っていますが、実際はそうではありません。今日は一緒にJavaのインターフェースと抽象クラスについて学んでいきましょう。


一、抽象クラス

抽象クラスを理解する前に、まず抽象メソッドについて理解しましょう。抽象メソッドは特殊なメソッドであり、宣言のみがあって具体的な実装がありません。抽象メソッドの宣言形式は次の通りです:

abstract void fun();

抽象メソッドはabstractキーワードで修飾する必要があります。クラスに抽象メソッドが含まれる場合、そのクラスは抽象クラスと呼ばれ、抽象クラスはクラスの前にabstractキーワードを付けて修飾する必要があります。抽象クラスには具体的な実装がないメソッドが含まれるため、抽象クラスを使ってオブジェクトを作成することはできません。

次に注意すべき点があります。『JAVAプログラミング思想』という書籍では、抽象クラスを「抽象メソッドを含むクラス」と定義していますが、その後、抽象メソッドを含まないクラスでもabstractで修飾するだけで抽象クラスになることがわかりました。つまり、抽象クラスは必ずしも抽象メソッドを含む必要はありません。個人的には、これは枝葉末節にこだわる問題だと思います。なぜなら、抽象クラスが抽象メソッドをまったく含まないのであれば、なぜ抽象クラスとして設計する必要があるのでしょうか。ですから、ひとまずこの概念を覚えておけば十分であり、深く追究する必要はありません。

[public] abstract class ClassName {
    abstract void fun();
}

ここからわかるように、抽象クラスは継承のために存在します。抽象クラスを定義しても継承しなければ、その抽象クラスを作成した意味がありません。なぜなら、それを使って何もできないからです。親クラスにおいて、あるメソッドを親クラスで実装しても意味がなく、サブクラスの実際のニーズに応じて異なる実装を行う必要がある場合、そのメソッドをabstractメソッドとして宣言できます。このとき、そのクラスもabstractクラスになります。

抽象メソッドを含むクラスを抽象クラスと呼びますが、これは抽象クラスには抽象メソッドしか含めないという意味ではありません。通常のクラスと同様に、メンバー変数や通常のメンバーメソッドを持つことができます。注意すべき点として、抽象クラスと通常のクラスの主な違いは次の3点です:

  • 1)抽象メソッドはpublicまたはprotectedでなければなりません(privateの場合、サブクラスに継承されず、サブクラスはそのメソッドを実装できないためです)。デフォルトではpublicになります。
  • 2)抽象クラスはオブジェクトの作成に使用できません;
  • 3)クラスが抽象クラスを継承する場合、サブクラスは親クラスの抽象メソッドを実装する必要があります。サブクラスが親クラスの抽象メソッドを実装しない場合、サブクラスもabstractクラスとして定義する必要があります。
  • その他の点では、抽象クラスと通常のクラスに違いはありません。

二、インターフェース

インターフェースは英語でinterfaceと呼ばれ、ソフトウェア工学において、インターフェースは他者が呼び出すためのメソッドや関数を広く指します。ここから、Java言語設計者の当初の意図を感じ取ることができます。それは振る舞いの抽象化です。Javaでは、インターフェースを定義する形式は次の通りです:

[public] interface InterfaceName {
 
}

インターフェースには変数とメソッドを含めることができます。ただし、注意すべき点として、インターフェース内の変数は暗黙的にpublic static final変数として指定されます(そしてpublic static final変数でなければなりません。privateで修飾するとコンパイルエラーになります)。一方、メソッドは暗黙的にpublic abstractメソッドとして指定され、public abstractメソッドでなければなりません(private、protected、static、finalなどの他のキーワードで修飾するとコンパイルエラーになります)。また、インターフェース内のすべてのメソッドは具体的な実装を持つことができません。つまり、インターフェース内のメソッドはすべて抽象メソッドでなければなりません。ここからインターフェースと抽象クラスの違いがうっすらと見えてきます。インターフェースは非常に抽象的な型であり、抽象クラスよりもさらに「抽象の」で、一般的にはインターフェース内に変数を定義しません。

クラスに特定のインターフェースを実装させるには、implementsキーワードを使用する必要があります。具体的な形式は次の通りです:

class ClassName implements Interface1,Interface2,[....]{
}

ここからわかるように、1つのクラスは複数の特定のインターフェースを実装できます。非抽象クラスがインターフェースを実装する場合、そのインターフェース内のすべてのメソッドを実装する必要があります。抽象クラスがインターフェースを実装する場合、そのインターフェース内の抽象メソッドを実装しなくても構いません。


抽象クラスとインターフェースの違い

1、構文レベルでの違い

  • 1)抽象クラスはメンバーメソッドの実装詳細を提供できますが、インターフェースにはpublic abstractメソッドしか存在できません;
  • 2)抽象クラスのメンバー変数はさまざまな型にできますが、インターフェースのメンバー変数はpublic static final型のみです;
  • 3)インターフェースには静的コードブロックと静的メソッドを含めることはできませんが、抽象クラスには静的コードブロックと静的メソッドを含めることができます;
  • 4)1つのクラスは1つの抽象クラスのみ継承できますが、1つのクラスは複数のインターフェースを実装できます。

2、設計レベルでの違い

1)抽象クラスはある事物の抽象化、つまりクラスの抽象化であり、インターフェースは振る舞いの抽象化です。抽象クラスはクラス全体を抽象化し、属性や振る舞いを含みますが、インターフェースはクラスの一部(振る舞い)を抽象化します。簡単な例を挙げると、飛行機と鳥は異なる種類の事物ですが、両者には共通点があります。それはどちらも飛べるということです。設計する際に、飛行機をクラスAirplaneとして設計し、鳥をクラスBirdとして設計できます。しかし、飛行という特性もクラスとして設計することはできません。なぜなら、それは単なる振る舞いの特性であり、ある種類の事物に対する抽象的な記述ではないからです。このとき、「飛行」をインターフェースFlyとして設計し、fly()メソッドを含め、AirplaneとBirdがそれぞれ自分の必要に応じてFlyインターフェースを実装できます。また、戦闘機や民間機など異なる種類の飛行機がある場合は、直接Airplaneを継承すればよく、鳥も同様に、異なる種類の鳥は直接Birdクラスを継承すればよいのです。ここからわかるように、継承は「であるかどうか」の関係であり、インターフェースの実装は「持っているかどうか」の関係です。クラスが抽象クラスを継承した場合、サブクラスは必ず抽象クラスの種類に属します。一方、インターフェースの実装は持っているかどうか、備わっているかどうかの関係です。例えば、鳥が飛べるかどうか(飛行という特性を備えているかどうか)で、飛べるならこのインターフェースを実装でき、飛べないならこのインターフェースを実装しません。

2)設計レベルが異なります。抽象クラスは多くのサブクラスの親クラスとして、テンプレート型の設計です。一方、インターフェースは振る舞いの規範であり、放射型の設計です。テンプレート型の設計とは何でしょうか。最も簡単な例として、皆さんはPPTのテンプレートを使ったことがあるでしょう。テンプレートAを使ってPPT BとPPT Cを設計した場合、PPT BとPPT Cの共通部分がテンプレートAです。それらの共通部分を変更する必要があれば、テンプレートAだけを変更すればよく、PPT BとPPT Cを再度変更する必要はありません。一方、放射型の設計とは、例えば、あるエレベーターに特定の警報器がすべて取り付けられている場合、警報器を更新する際にはすべてを更新しなければなりません。つまり、抽象クラスの場合は、新しいメソッドを追加する必要があれば、抽象クラスに直接具体的な実装を追加でき、サブクラスを変更する必要はありません。しかし、インターフェースの場合はそれができません。インターフェースが変更された場合、そのインターフェースを実装するすべてのクラスが相応の変更を行う必要があります。

次に、インターネット上で最も広く広まっている例を見てみましょう。ドアと警報の例です。ドアにはopen()とclose()2つの動作があります。このとき、抽象クラスとインターフェースを通じてこの抽象概念を定義できます:

abstract class Door { public abstract void open(); public abstract void close(); }

または:

interface Door { public abstract void open(); public abstract void close(); }

しかし、ドアに警報機能を持たせる必要がある場合、どのように実装すればよいでしょうか。以下に2つの考え方を示します:

1)この3つの機能をすべて抽象クラスの中に入れます。しかし、そうするとこの抽象クラスを継承するすべてのサブクラスが警報機能を備えることになりますが、ドアによっては必ずしも警報機能を備えていない場合があります;

2)この3つの機能をすべてインターフェースの中に入れます。警報機能を必要とするクラスは、このインターフェースのopen( )とclose( )を実装する必要がありますが、そのクラスにはopen( )とclose( )という2つの機能がそもそもないかもしれません。例えば、火災警報器などです。

ここからわかるように、Doorのopen()、close()とalarm()は根本的に2つの異なる範疇に属する振る舞いです。open()とclose()はドア自体が本来持つ振る舞いの特性であり、alarm()は拡張された付加的な振る舞いです。したがって、最善の解決策は、警報を単独でalarm()振る舞いを含むインターフェースとして設計し、Doorをopenとcloseの2つの振る舞いを含む単独の抽象クラスとして設計することです。さらに、警報ドアを設計してDoorクラスを継承し、Alarmインターフェースを実装します。

interface Alram { void alarm(); } abstract class Door { void open(); void close(); } class AlarmDoor extends Door implements Alarm { void oepn() { //.... } void close() { //.... } void alarm() { //.... } }

原文アドレス:http://www.cnblogs.com/dolphin0520/p/3811437.html