インターフェース

インターフェースとは

抽象クラスの概念をさらに推し進めたのが、インターフェースという概念です。抽象クラスがインスタンスを生成できないように、インターフェースもインスタンスを生成することができませんが、使用方法などは抽象クラスとは根本的に異なります。

サンプルプログラム

インターフェースについて説明する前に、まずは以下のクラスを入力してみてください。

SampleClassEx401.java
package exday4;

public class SampleEx401 {

	public static void main(String[] args) {
		//	携帯電話クラスのインスタンスを生成
		CellPhone cp = new CellPhone("hoge@email.com","090-1234-5678");
		//	携帯電話クラスで、電話とメールを送る
		cp.call("011-123-4567");
		cp.sendMail("fuga@email.com");
		//	電話インターフェースでインスタンスにアクセス。
		IPhone phone = (IPhone)cp;
		phone.call("011-987-6543");		//	電話をかける
		//	phone.sendMail("foo@email.com");		//	メールの送信メソッドは利用できない。
		//	メールインターフェースでインスタンスにアクセス。
		IEmail mail = (IEmail)cp;
		mail.sendMail("bar@email.com");	//	メールを出す
		//mail.call("011-222-3333");		//	mailインターフェースでは、電話の機能を利用できない。

	}
}
CellPhone.java
package exday4;

//	携帯電話クラス
public class CellPhone implements IPhone,IEmail{
	//	メールアドレス
	private String mailAddress;
	//	電話番号
	private String number;
	//	コンストラクタ(メールアドレスと電話番号を設定
	public CellPhone(String mailAddress,String number){
		this.mailAddress 	= mailAddress;
		this.number			= number;
	}
	//	指定したメールアドレスにメールを送信する
	public void sendMail(String address){
		System.out.println(address+"に、"+this.mailAddress+"からメールを出します。");
	}
	//	指定した番号に電話をかける
	public void call(String number){
		System.out.println(number+"に、"+this.number+"から電話をかけます。");
	}
}
IPhone.java
package exday4;

public interface IPhone {
	//	指定した番号に電話をかける
	public void call(String number);
}

IEmail.java
package exday4;

public interface IEmail {
	//	メールを送る
	public void sendMail(String address);
}
実行結果
011-123-4567に、090-1234-5678から電話をかけます。
fuga@email.comに、hoge@email.comからメールを出します。
011-987-6543に、090-1234-5678から電話をかけます。
bar@email.comに、hoge@email.comからメールを出します。

CellPhone(携帯電話)クラスには、電子メールを送る機能と電話をかける機能があります。携帯電話には、電話としての側面と、メール送受信装置としての側面があるわけです。このクラスのように、一つのクラスに複数の側面がある場合によく用いられるのがインターフェースです。

インターフェースの記述方法

CellPhone.javaの4行目に注目してください。クラス名の後ろに、implementsと書かれており、,で区切られて、IPhone、IEmailと書かれています。この、implementsの後に追加されているものが、インターフェースです。

インターフェースの定義
class (クラス名) implements (インターフェース名),(インターフェース名)…{
    …
}

継承の場合と違い、インターフェースは複数定義することができます。その場合、インターフェース名の間を","で区切ります。また、通常、インターフェース名は、このンプル内で用いられている「IPhone」「IEmail」などといったように、"I"から始まる英単語を用いるのが普通です。

文法上、Iをつけなくてはならないという決まりがあるわけではありませんが、java言語のプログラミングのマナーとして、そういう命名方法が推奨されているのです。

キャスト

IPhone、およびIEmailには、それぞれもともとCellPhoneクラスが持っているcall()メソッド、および、sendMail()メソッドが記述されています。なぜわざわざ、そのようなことをしなくてはならないのでしょうか?

実は、こうすることにより、このCellPhoneクラスは、IPhone、および、IEmailという、架空の「クラス」としてふるまうことができるのです。たとえば、SampleEx401.javaの12行目を見てください。IPhone型の変数、phoneを用意し、そこに、先頭に(IPhone)という文字列を追加する形でcpを代入しています。

インスタンスのキャスト
IPhone phone = (IPhone)cp;  ← CellPhoneクラスを、IPhone型にキャスト
IEmail mail = (IEmail)cp;        ← CellPhoneクラスを、IEmail型にキャスト

この処理をキャストと言い、あるクラスを、親クラス、もしくはインターフェースの型に変更して代入するときなどに使用します。16行目でも、同様の処理が行われています。

キャストをすると、もとは同じクラスのインスタンスであったとしても、キャストした型のメンバしか使用できなくなります。たとえば、phoneは、call()メソッド、mailはsendMail()メソッドしか使えなくなります。

ためしに、14行目、および18行目のコメントアウトを外してみましょう。ビルドエラーが発生します。もとは、同一のCellPhoneクラスオブジェクトインスタンスであるはずのものであるにもかかわらず、実装されているインターフェースのメンバしか使用できなくなるのです。(図3-1.参照)

図4-1.クラスとインターフェースの関係性

親クラスのコンストラクタの呼び出し

インターフェースの必要性

では、なぜわざわざこのようなことをする必要があるのでしょうか?この携帯電話クラスの例を見ても分かる通り、javaのクラスには、様々な側面があります。さらに、大規模なjavaのアプリケーションを開発した場合、大勢のプログラマーが、多数のクラスを作成し、それらが複雑に関連していきます。

そういった場合、電子メールの機能に関するクラスを実装する人にとっては、IEmailインターフェースで記述されているメンバを使えれば十分です。また、電話の機能に関するクラスも、IPhoneクラスのメンバが使えれば十分でしょう。そのため、インターフェースを利用して、強制的にクラスの機能を制限することにより、余計なメンバへのアクセスの制限をかけることができるのです。

これにより、プログラマーは、自分の必要なメンバがわかっているので、間違いをおこさず、必要なメンバーだけを利用することができます。これにより、クラスを間違った方法でアクセスされる危険性が減少し、プログラミングの効率もアップします。

インターフェースと定数

サンプルプログラム

インターフェースは、抽象メソッドだけではなく、定数を定義することができます。以下に、インターフェースに定数を定義したサンプルがありまる。実行してみましょう。

SampleClassEx402.java
package exday4;

public class SampleEx402 {

	public static void main(String[] args) {
		Tank t = new Tank();
		System.out.println(t.getName()+"の武器の名前:"+Tank.WEAPON_NAME);
		System.out.println(t.getName()+"の武器のタイプ:"+Tank.TYPE_NAME);
		System.out.println();
		t.attack();	//	攻撃
	}

}
Tank.java
package exday4;

public class Tank extends Weapon implements ICar,ICannon{
	//	コンストラクタ
	public Tank(){
		super("戦車");
	}
	//	大砲を撃つ
	public void fire(){
		System.out.println("砲撃");
	}
	//	移動
	public void move(){
		System.out.println("移動");
	}
	//	攻撃
	public void attack(){
		System.out.println(this.getName()+"の攻撃方法");
		System.out.println("--------------");
		fire();
		move();
		System.out.println("--------------");
	}

}
Weapon.java
package exday4;

//	武器クラス
public abstract class Weapon {
	private String name="";
	//	コンストラクタ
	public Weapon(String name){
		//	武器の名前を設定
		this.name = name;
	}
	//	武器の名前を取得
	public String getName(){
		return this.name;
	}
	//	攻撃メソッド
	public abstract void attack();
}

ICannon.java
package exday4;

//	大砲インターフェース
public interface ICannon {
	public String WEAPON_NAME="cannon";
	//	大砲を撃つ
	public void fire();
}

ICar.java
package exday4;

//	自動車インターフェース
public interface ICar {
	public String TYPE_NAME = "car";
	//	移動メソッド
	public void move();
}

実行結果
戦車の武器の名前:cannon
戦車の武器のタイプ:car

戦車の攻撃方法
--------------
砲撃
移動
--------------

戦車(Tank)クラスは、自動車(ICar)インターフェース、および、大砲(ICannon)インターフェースを実装しています。これらのインターフェースでは、は変数WEAPON_NAMEおよびTYPE_NAMEが定義されていますが、これらは、通常のクラスであれば、finalおよびstaticとしたものと同じです。つまり、定数として使用され、値として利用できます。(SampleClassEx402.javaの7行目から8行目参照)

これらは定数なので、基本的にアルファベットの大文字をつかって記入します。このサンプルでは、Tankクラスの定数として使われています。

継承との併用

また、このTankクラスは、もともと抽象クラスである武器(Weapon)クラスを継承しています。このように、あるクラスを継承したクラスにインターフェースを追加する場合は、以下のような書式になります。

インターフェースの定義
class (クラス名) extends (親クラス名) implements (インターフェース名),(インターフェース名)…{
    …
}

implementsは、extendsの後にきます。このように、継承とインターフェースの両方を記述するケースは、インターフェースを用いるようなプログラミングの場面でよく用いる表現ですので、覚えておきましょう。

練習問題 : 問題3.