目次

インタフェース(interface)

  • クラスメンバを操作する手順としての抽象メソッドとstatic変数の集合体のこと。
  • インタフェースを使うことで、インタフェースを実装するクラスはすべてメソッド仕様を統一することができる。

インタフェースを使う場合の信条

  • インタフェースに集中する。
    • インタフェースを考えること(コンポーネントがどのように機能するかではなく、どのコンポーネントが役に立つかを考える)で、さらに抽象化を図る。
    • リファクタリングでも、内部実装を変更しても外見上の振る舞いを変更しないようにするためにインタフェースに注意を払う。

インタフェースの特徴

  • インタフェースにはいかなる実装も含まれない。
  • インタフェース内のメソッドは暗黙のうちにabstractであり、publicである。
  • インタフェースにクラスメソッドを含むことはできない。
    • なぜならばabstract付きのクラスメソッドはありえないから。
  • インタフェース内のフィールドは定数だけ定義できる。
    • インスタンス変数は定義できない。
  • インタフェースはインスタンスを生成できない。よって、コンストラクタは存在しない。
  • 一般のスーパークラスと同様に、継承したサブクラスをインタフェース型の変数で操作することはできる。
  • インタフェースは通常のクラスの継承と同じスタイルで継承できる。ただし、extendsキーワードではなく、implementsキーワードを使う。
    • Javaではインタフェースをクラスに組み込むことをインタフェースを実装するという。
  • クラスを拡張(継承)するのと同じように、extendsキーワードを使ってインタフェースを拡張することができる。
    • このとき親になるインタフェース(継承される側)をスーパーインタフェース、子になるインタフェース(継承する側)をサブインタフェースと呼ぶ。
    • この場合、クラス拡張と異なり、複数のスーパーインタフェースを指定することが可能である。
  • メソッドシグネチャとstatic finalフィールドしか含まない抽象クラスはインタフェースとして宣言する。
  • instanceof演算子により、インタフェース実装を判定することができる。
    • 空のインタフェース(マーカーインタフェース)を用意して、オブジェクトに対するスイッチ情報を与えるという使い方ができる。

例:

interface MyIntr1 {
	…
}

interface MyIntr2 {
	…
}

interface dispable {}	//マーカーインタフェース用

class SubCls1 implements MyIntr1, MyIntr2 {	//複数のスーパーインタフェースMyIntr1,MyIntr2を指定
	…
}

class MyClass1 extends SubCls1 implements dispable {
	public void display() {
		System.out.println("dispableを実装している")
	}
}

class MyClass2 extends SubCls1 {	//マーカーインタフェースのdispableを実装していない
	…
}

 という定義があるとき、instanceof演算子を使って次のような処理が可能である。

public class TestClass {
	public static void main(String args[]) {
		 SubCls1 cls = new SubCls1();
		 if (cls instanceof MyIntr1)	//真
			…	//こちらが実行される
		 else
			…
		 if (cls instanceof MyIntr2)	//真
		 	…	//こちらが実行される
		 else
			…
		 
		 SubCls1 array[] = new SubCls1[1];
		 array[0] = new MyClass1();
		 array[1] = new MyClass2();
		 
		 for (i = 0; i < array.length; i++) {
		 	 if (cls instanceof dispable)
			 	…	//array[0]のときに実行される。
			 else
				…	//array[1]のときに実行される。
		 }
	}
}
  • 抽象クラスとよく似た目的で用いられる。
  • Javaのインタフェースは、カテゴリーを作るためだけに使われる特殊なクラスのことである。
    • 抽象クラスからクラスのグループ化の機能だけを抽出したものだけと想像すればよい。
  • インタフェースはクラスの継承と合わせて使える。そして、複数のインタフェースを同時に実装できる。
    • Javaでは多重継承*1ができない仕様になっている。しかし、どうしても1つのクラスを色々なカテゴリーに所属させたいという画面があるかもしれない。これを実現する簡単な実現方法は多重継承だが、Javaでは多重継承はできないので、このアプローチは使えない。そこで、インタフェースの複数同時に実装でき、継承も合わせて行えるという機能を利用することで、実質的に多重継承とほぼ同じ状況を実現できるのである。
class MyClass extends SuperClass implements MyInterface1, MyInterface2 {
	…
}

インタフェースの一般形式

interface 名前 {
	型 フィールド名 = 初期値;	//フィールドは定数になるので初期値が必要。
	型 メソッド名(引数,…);
}

 インタフェース内のメンバ(フィールドとメソッド)のデフォルト属性はコンパイル側で次のように解釈される(明示してもよいが、一般には省略される)。

  • フィールドのデフォルト属性
    • public static final 型 フィールド名 = 初期値;
  • メソッドのデフォルト属性
    • abstract public 型 メソッド名(引数,…);

分類

  • インナークラスは4種類あるが、それらは2つの基本的な形にグループ分けができる。
    1. ネストされたトップレベルのクラスならびにインタフェースが外側から見える場合
      • これらのクラスは別のクラスの内部に存在するが、包含する側のクラスの外から見えるようにできる。また、ネストされたインタフェースはそれらを包含する側のクラスの外から常に見える。
      • ネストされたクラスをその包含クラスの外から見えるようにするためには、staticキーワードを付ける。こうすることで、それがネストされたトップレベルのクラスであることを示す。
      • この形では包含する側のクラスはまさしくパッケージのような役割を持つ。
    2. ネストされたトップレベルのクラスならびにインタフェースが外側から見えない場合
      • メンバークラス、ローカルクラス、無名クラスが該当する。
      • これらのクラスはヘルパーとして他のクラス内に存在する。

インタフェース内の抽象メソッド

  • インタフェース内のすべてのメソッドは暗黙のうちにpublic abstractであると判断される。そのため、修飾子を省略しても問題ない。
    • 実装クラス側では抽象メソッドを実装するときに、publicを付ける必要がある。
      • アクセス修飾子を何も付けないとデフォルトのパッケージ属性(同じパッケージの内部でのみでpublic)となってしまう。これはpublic修飾子よりも弱いアクセシビリティなのでコンパイルエラーになる。また、protected,privateといったアクセス修飾子も明らかにpublicよりも弱いアクセシビリティなので、付けることはできない。

インタフェースの実装クラス

  • インタフェースを実装するクラスはextendsの代わりにimplementsというキーワードを使って、カテゴリーに属するクラスを宣言する。
  • Javaでは継承によって受け継いだメソッドをオーバーライドする場合、あるいは抽象メソッドを実装する場合には、元となるメソッド・抽象メソッドよりも弱いアクセシビリティ(アクセシビリティを狭める)を設定することはできない。
    • 例えば、publicで宣言されたメソッドを、privateを使ってオーバーライドする行為は、アクセシビリティが弱まったことを意味し、コンパイル時にエラーが発生する。
    • これは多態性を実現するためには必要な制約である。継承によってクラスをグループ化していく上では、同じカテゴリーに属するすべてのクラスのインタフェースが統一されていなければならない。つまり、カテゴリー内のすべてのクラスのオブジェクトから、基点となるクラス(カテゴリー)で定義されたメソッドを呼び出せるようになっている必要がある。もし、基点となるクラスで定義されたものよりも弱いアクセシビリティに変更できてしまったならば、持っているべきメソッドがなくなる(見えない、アクセスできない)状況になってしまうからである。

インタフェースの継承

  • 既存のインタフェースを継承して新しいインタフェースを定義することが可能である。
  • インタフェースを継承するには、クラスの継承と同様にextendsキーワードを使えばよい。
  • サブインタフェースはスーパーインタフェースで定義されているすべての定数フィールドと抽象メソッドを継承する。
  • サブインタフェースの中でスーパーインタフェースと同じ定数フィールドや抽象メソッドを定義した場合は、通常のクラスの継承のときと同様に定数フィールドの値は新しく定義された値に上書きされ(「サブインタフェースの定数フィールドがスーパーインタフェースの定数フィールドを隠蔽する」と表現される)、メソッドはオーバーライドされる。
  • 複数のスーパーインタフェースを持つインタフェースは、すべてのインタフェースで定義されている抽象メソッドのうち、いずれか1つでも実装し残したものがある場合は、そのクラスはabstractキーワードをつけて抽象クラスとして宣言しなければならない。

参考文献

  • 『新Java言語入門 シニア編』
  • 『Javaの格言』
  • 『10日でわかるオブジェクト指向』
  • 『プレファクタリング リファクタリング軽減のための新設計』


*1 多重に継承すること。つまり、2つ以上のクラスをスーパークラスとしてクラスを定義すること。