[???] /
[Java FAQ] / [S015]
S015: インタフェース/アブストラクトクラス - interface/abstract class
[S015 Q-01]
interface って何ですか?
[S015 A-01]
具体的な処理内容は決めずに、呼び出し形式のみを規定する型です。
インタフェースもクラスと同じように宣言して使用します。宣言には class
キーワードの代わりに、インタフェースであることを示すために interface
キーワードを使います。
interface Quoter {
public String quote(String s);
}
ただし、宣言中に書けるメソッドは abstract メソッド(§12.01 コラム)のみ
です。したがってインタフェースはメソッドの具体的な処理内容は規定せずに、
その呼び出し形式のみを規定することになります。
メソッドの具体的な処理内容(実現方法)を与えるには、メソッドを別途、クラ
スに書きます。クラスがインタフェースの実現方法を含んでいることを示す
ために implements キーワードの後にインタフェース名を書きます。
このクラスを書くことを「インタフェースを実装する」と言います。
(一つのクラスで複数のインタフェースを実装する事もできます。その場合は
インタフェース名をカンマで区切ります。)
class JapaneseQuoter implements Quoter {
public String quote(String s) {
return "「" + s + "」";
}
}
実装するクラスでは全てのメソッドを実装しなければなりません。
(ただしそのクラスを abstract クラス(S015-04)にすると、全てのメソッドを
実装する必要はなくなります。)
インタフェースのメソッドを利用する際には、必ず実装したクラスをインスタン
ス化します。インタフェースそのものをインスタンス化することはできません。
Quoter q = new JapaneseQuoter();
System.out.println(q.quote("Java"));
インタフェースの宣言に extends 節を書くと、他の複数のインタフェースの内容
を継承し取り込むことができます。これにより、新たなメンバを加え拡張をしたり、
複数のインタフェースをまとめて一つの名前を与えることができます。
import java.awt.event.*;
interface FocusableKeyListener
extends KeyListener, FocusListener {
}
インタフェースはメンバとしてメソッドの他に、フィールド、クラス、インタフェー
スを含むことが出来ます。
(メンバであるクラス、インタフェースについては §13「内部クラス」を参照)
これらメンバーはすべて public でなければなりません。明示的に public と書かな
かった場合にも public とみなされます。public の他にもメンバの修飾子にはメンバ
の種類により制限され暗黙のうちに指定されるものがあります。表にまとめると、
インタフェース中のメンバの修飾子
メソッド | public | abstract | 非static |
フィールド | public | static | final |
クラス | public | static | |
インタフェース | public | static | |
となります。これらと矛盾する指定をするとコンパイルエラーになります。
インタフェースのメソッド宣言部分をコピーして、実装クラスでメソッドを書く際に
利用しようとするとコンパイルエラーになることがあります。インタフェースでは何も
付けなくとも public ですが、クラスでは public を明示しなければならないからです。
***コラム「abstractメソッドって何ですか?」**********************************************
abstract(抽象)メソッドは、実装を伴なわない呼び出し形式のみを宣言したメソッドです。
abstractメソッドの宣言では、処理の本体を中括弧に括って与えるかわりに、";" を記述し
ます。また、そのメソッドがクラスのメンバとして宣言される場合には、メソッド自身、
および、それをメンバとするクラスにabstract修飾子を付加する必要があります。その他の
部分、つまり、戻り値型やメソッド名、仮引数宣言、throws節などは通常のメソッドと同様
です。
例えば、以下のクラス宣言はvoid型の引数なしabstractメソッドを宣言しています。
abstract class AbstractClass {
abstract void abstractMethod();
}
インタフェースのメンバは暗黙的にabstractであると見なされるため、abstract修飾子は必要
ありません。また、publicなメソッドであるとも見なされるので、public以外のアクセスレベ
ル修飾子を指定することはできません。
例えば、以下のインタフェース宣言はint型を返すStringを引数とするabstractメソッドを宣言
しています。
interface AnInterface {
int interfaceMethod(String param);
}
以下では、abstract宣言されたクラス、および、全てのインタフェースを便宜的に抽象型と
呼びます。また、abstractクラスをextendsするクラスや、インタフェースをimplementsする
クラスをサブタイプと呼ぶことにします。
abstract宣言されたメソッドは、それを宣言した抽象型のサブタイプで実際の処理内容を与え
ます。このとき、サブタイプをabstract宣言すれば、abstract宣言されているメソッドの一部
または全部の実装を与えずにおくことも可能で、その場合、改めてabstractメソッドを宣言す
る必要はありません。
abstractメソッドは、機能とその呼び出し形式のみを規定し、具体的な実現方法は規定しない
メソッドであると捉えることができます。あるクラスで必要となる機能の一部に関して、実際
の処理内容が異なる複数のサブクラスが考えられ、それらのサブクラス群で共通となる標準的
な処理内容を与えることが困難な場合に、その機能の実装をサブクラスに委ねるためにabstract
メソッドを用います。こういったものの極端な形が、全てabstractメソッドで構成されるインタ
フェースです。
サブクラス群で共通となる標準的な処理内容を与えることが可能な場合もあり、この場合には
abstractメソッドを使わずにデフォルトの処理をスーパークラスで実装しておき、サブクラス
が必要に応じてオーバライドするといった手法も用いられます。HttpServletのdoGet()メソッド
やdoPost()メソッドで、GETメソッド、POSTメソッドをサポートしていないというエラーをHTTP的
に返す処理が記述されているのや、WindowAdapterで各イベントを処理するメソッドに空の実装が
与えられているのは、この手法の例です。
抽象型は、直接インスタンス化することができません。仮に抽象型をインスタンス化できてし
まったら、実装の与えられていないメソッドを持つオブジェクトが存在することになってしまい
ますから、問題が起こることは容易に想像がつくでしょう。このことから、クラスの持つ機能の
うち、必要不可欠だがバリエーションが存在する機能の実装責任を明示的にサブクラスに与えた
い場合に、abstractメソッドを使うのが効果的であると言えるでしょう。
****************************************************************************************
[S015 Q-02]
interface のメリットって何ですか?
[S015 A-02]
以下のメリットがあります。
1. インタフェース型をimplementsしたクラスを複数作れることによるメリット:
インタフェースに対して作られたプログラムは、そのインタフェースを
implements した様々なクラスに対して実行可能です。
例えば、java.util.Collections#sort メソッドは、java.util.List
インタフェースを引数に取ります。このため、Listインタフェースを
implements している java.util.ArrayList クラスや java.util.LinkedList
クラスのオブジェクト(あるいは、あなたが作成した新しいクラスのオブジェクト)
を引数にとり、ソートすることができます。
これと同様のメリットが、final ではないクラスにもあります。
2. Javaにinterfaceが存在することのメリット:
1つのオブジェクトに複数の型を持たせる【★正確ではない】ことができます。
クラスは、複数のクラスを同時に extends できないため、継承関係の無い複数
のクラス型をもつオブジェクトを作成することは出来ません。しかしインタフェース
を用いることにより、複数の継承関係の無い型をもつオブジェクトを作成することが
出来ます。
例えば、キーボードによる入力を受け取れることを表す java.awt.event.KeyListener
インタフェース型と、マウスによる入力を受け取れることを表す java.awt.event.MouseListener
インタフェース型とを同時にもつオブジェクトを、両方のインタフェースを
implements することによって作ることが出来ます。
もし、Java にインタフェースがなく KeyListener と MouseListener がクラスだったなら、
この2つの型をもつオブジェクトを作ることはできませんでした。
3. abstract classではなく、interfaceを使うことのメリット:
implementsしたクラスに、他のクラスをextendsする自由を残します。
abstract class A を作成した場合、A を extends するクラスは、他のクラスを extends
することはできません。しかし、A をインタフェースとすれば、A を implements する
クラスが、他のクラスを extends することができます。
関連記事: [JavaHouse-Brewers:4502][JavaHouse-Brewers:4549][JavaHouse-Brewers:9053]
[S015 Q-03]
interfaceのメソッドを呼び出しているのですが、実際に実行される
コードはどこにあるのですか?
[S015 A-03]
そのオブジェクトをインスタンス化したときに、指定されたクラス(あるいはその
スーパークラス)にあります。
インスタンスメソッドを呼び出したとき実行されるコードは、interfaceではなく、
実行時のオブジェクトのクラスに定義されています。このクラスは、そのオブジェクト
をインスタンス化したときに指定されたクラスです。実際に実行されるコードを特定する
ためには、オブジェクトがどこでインスタンス化されているかを調べなければなりません。
例えば、ソースコード中に以下のような記述があるとします。
interface Foo {
void bar();
}
class FooImplementer implements Foo {
public void bar() {
System.out.println("FooImplementer");
}
}
class FooFactory {
static Foo createFoo() {
return new FooImplementer();
}
}
class Test {
void method() {
Foo foo = FooFactory.createFoo();
foo.bar();
}
}
ここで、foo.bar()を呼び出したときに実行されるコードは、interface Foo にではなく、
foo が指しているオブジェクトのクラスに定義されています。このオブジェクトは、
プログラムをたどるとcreateFooでインスタンス化されていており、指定されているクラスは
FooImplementerです。foo.bar()の呼び出しによって、FooImplementerで定義されている
bar()が呼び出されます。
[S015 Q-04]
abstract クラスってなんですか?
[S015 A-04]
一部のメソッドの具体的な処理内容をサブクラスに任せることを明示したクラスです。
Java には、メソッドの具体的な処理内容を決めずに、呼出し形式のみを規定するため
に、abstract メソッド(§12.01 コラム)というものがあります。この abstract
メソッドをクラスに含める際には、そのクラスを abstract クラスとして宣言しなけ
ればなりません。abstract クラスを宣言するには、通常のクラス宣言の class の前に
abstract キーワードを付けます。
abstract class DocumentFormatter {
abstract String emphasize(String s);
abstract String getTitleHeader();
void formatTitle(String s) {
System.out.print(getTitleHeader());
System.out.print(emphasize(s));
}
}
メソッドの具体的な処理内容(実装)は、abstract クラスを継承したクラスに書きます。
いくつかの abstract メソッドのみを実装し、残りは処理内容を決めないままにする場合
には、そのクラスを再び abstract クラスにしなければなりません。
(処理内容を書かないメソッドについては、abstract メソッドとして書いても何も書か
ないでも構いません。)したがって、abstract クラスを(直接あるいは間接に)継承して
いる非 abstract クラスは abstract メソッドを全て実装している事になります。
abstract class EnglishDocumentFormatter
extends DocumentFormatter {
String getTitleHeader() {
return "Title:";
}
}
class SimpleDocumentFormatter
extends EnglishDocumentFormatter {
String emphasize(String s) {
return s.toUpperCase();
}
}
abstract クラスそのものはインスタンス化できません。インスタンスメソッドを使用
する際には、必ず継承した非abstract クラスをインスタンス化します。
DocumentFormatter formatter
= new SimpleDocumentFormatter();
formatter.formatTitle("Abstract class");
クラスが abstract メソッドを一つも含んでいなくても abstract クラスにできます。
通常この場合には、継承し何らかの変更をして使われることを意図しています。
[S015 Q-05]
abstract クラスのメリットって何ですか?
[S015 A-05]
クラスのメリットに加えて、abstractメソッドを持つことがメリットになります。
abstractクラスではまず次のメリットがあります。
・abstractメソッドを持つことができる
abstractメソッド(S015-01コラム)を持つことにより、振舞いをサブクラスで
定義するように明示することができます。また、abstractメソッドが実装されてい
ない場合にインスタンスを生成しようとすると、実装されていないので生成できな
いというエラーをコンパイル時に検知することができます。
また、abstractクラスは次のような「(abstractクラスに限らない一般的な)クラスの
メリット」も持ちあわせています。
・クラスを 1つだけ extendsに指定できる
これは「複数のクラスを継承できない」のでデメリットのように思えるかもしれません。
しかし、複数のクラスを継承できないことによって、実装したメソッドの場所を探す
手間を軽減することができると考えられるでしょう。
・abstractではないメソッドや、変数をメンバに持つことができる
インタフェースの全てのメソッドは public 宣言した abstractメソッドです。それに
対して、クラスのメソッドは、宣言だけではなく、実装も書くことができます。この
実装はそのサブクラスでオーバライドすることもできます。これとは逆に、メソッドを
final宣言することによって、オーバライドできないようにする、すなわちサブクラスに
おいても、finalで宣言されたメソッドの実装が実行されることを保証することもできます。
また、インタフェースの変数は public static finalで宣言された(定数の意味しか持たない)
ものだけです。しかしクラスの変数は値をプログラムロジックの中で代入できます。
[S015 Q-06]
abstract class の abstract メソッドを呼び出しているのです
が、実装はどこにあるのですか?
[S015 A-06]
S015-03で説明した通り、「呼び出そうとしたオブジェクト」のクラスに
定義されたメソッドが呼び出されます。
詳細は S015-03 をご覧ください。
[S015 Q-07]
interface と abstract class の違いは何ですか?
[S015 A-07]
S015-02 で説明した、interface 独特の特徴と、S015-05 の abstract
class の特徴を照らし合わせれば答えが見つかります。
尚、言語仕様的な違いは S015-01/S015-04 及び Java 言語仕様を参照し
てください。
関連記事: [JavaHouse-Brewers:7370]
[S015 Q-08]
interface と abstract class をどのように使い分けるのですか?
[S015 A-08]
S015-02 及び S015-05 をご参照下さい。
関連記事: [JavaHouse-Brewers:9021] [JavaHouse-Brewers:8973] [JavaHouse-Brewers:8926]
[JavaHouse-Brewers:7608] [JavaHouse-Brewers:7423] [JavaHouse-Brewers:7422]
[JavaHouse-Brewers:7401] [JavaHouse-Brewers:7370]
[S015 Q-09]
interface に interface を implements しようとするとコンパ
イルエラーになります。どうしてですか?
[S015 A-09]
言語仕様上許されていないためです。
同種のものは extends を、そうでない場合は implements を使用します。
interface Interface1 { }
interface Interface2 extends Interface1 { }
class Class1 { }
class Class2 extends Class1 { }
class Class3 implements Interface1 { }
関連記事: [JavaHouse-Brewers:39311]
[S015 Q-10]
複数の interface を extends / implements できるのに、複数
の class を extends できないのはどうしてですか?
[S015 A-10]
言語仕様として、実装の多重継承を禁止しているためです。
まず、継承とは何を継承しているかを考えてみましょう。
(1)interface を継承するということは、その interface の外部仕様と型
を継承することになります。
(class に対して implements する場合も全く同じです)
(2)class を継承するということは、そのクラスの外部仕様と型、及び実
装を継承することになります。
つまり、「実装の継承」という物が存在するか否かが異なるわけです。
Java では実装の継承については、多重継承を許していません。この理由
は、実装された同じシグニチャのメソッドを持つ別のクラスを多重継承し
た場合の名前の衝突(と呼ばれる問題)や、菱形継承と呼ばれる、多重継承
したそれぞれのクラスの親に当たるクラスが同一クラスである場合の問題
などを発生させない為です。
複数のクラスの実装を利用する新たなクラスを作成したい場合は、委譲を
用います。
委譲を用いて外部仕様の再利用を行うことについての良い説明が以下から
参照できます。
news:<sfevhmxqgjr.fsf@java-house.etl.go.jp>
http://java-house.etl.go.jp/~takagi/archive/fj.comp.lang.java-interface-Sep1998/000066.html#body
なお、複数の interface を継承/実装する場合も、名前の衝突が発生する
場合がありますが、通常は単に共通の仕様を持つという意味にしかならない
ため、問題になりません。
関連記事: [JavaHouse-Brewers:7422]
contributor: Shin
コメントの送り先 Java FAQ BBS