インタフェース

<概要>インタフェースについて学びます。インターフェイスと言った方が分かり易いかも?

例題14-1 フィールドとメソッドを持つインタフェースを宣言する。

○プログラム

interface Math{
  double PI=3.14;
  void show();
}


○解説

インタフェース( interface )は抽象クラスに似ています。
抽象クラスはサブクラスに、どのようなメソッドを宣言しなければならないかということを強要していました。
インタフェースも同様に、それを実装したクラスに対して共通の雛形、操作性を提供することを目的としています。
具体的にどのような処理を行うかは実装したクラスに委ねられますが、
とりあえず、インタフェースにあるフィールドとメソッドは既知の名前で呼び出すことが出来るからです。

インタフェースの特徴をまとめます。

インタフェースは参照型の一種です。
・インタフェースが持つフィールドは必ず定数です。
・インタフェースが持つメソッドは必ず抽象メソッドです。
・インタフェースはインスタンスを作ることは出来ません

抽象クラスは一つ以上の抽象メソッドを持ちましたが、
インタフェースのメソッドは全て抽象メソッドです。

インタフェースを宣言するには interface という予約語を使います。
それにつづく単語 Math がインタフェースの名前ですね。

2. double PI=3.14;

インタフェースで宣言されているフィールドは、自動的に public static final になります。
つまり、public static final double PI=3.14; と宣言したのと同じです。

3. void show();

インタフェースで宣言されているメソッドは、自動的に public abstract になります。
つまり、public abstract void show(); と宣言したのと同じです。

例題14-2 インタフェースを実装する。

○プログラム

interface Math{
  double PI=3.14;
  void show();
}

class Circle implements Math{
  double r;
  public Circle(double r){
    this.r=r;
  }
  public void show(){
    System.out.println("面積="+PI*r*r);
  }
  public static void main(String[] args){
    Circle c=new Circle(12.3);
    System.out.println("円周率="+PI);
    System.out.println("半径="+c.r);
    c.show();
  }
}


○実行結果

D:\atsushi\Java\P108>java Circle
円周率=3.14
半径=12.3
面積=475.0506000000001
-- Press any key to exit (Input "c" to continue) --

○解説

インタフェースを実装するには implements という予約語を使います。
実装とは、メソッドの本体を定義することを意味します。
継承と同じで、実装したクラスはインタフェースの全てのフィールドとメソッドを持つことになります。

interface Math{
  double PI=3.14;
  void show();
}


は、

interface Math{
  public static final double PI=3.14;
  public abstract void show();
}


と同じでしたから、
static フィールド PI にはインスタンスなしで直接アクセスできますし、
 → System.out.println("円周率="+PI);
非 static メソッド show にはインスタンスを通して間接アクセスします。
 → c.show();

○補足

3.14*12.3*12.3 の正確な計算結果は 475.0506 です。
実行結果では 475.0506000000001 となっているので、
精度は最高でも小数点以下12桁ということになりますね。

例題14-3 クラスの継承とインタフェースの実装を同時に行う。

○プログラム

interface Math{
  double PI=3.14;
  void show();
}

class Point{
  int x,y;
}

class Rect extends Point implements Math{
  int width,height;
  public Rect(int x,int y,int width,int height){
    this.x=x;
    this.y=y;
    this.width=width;
    this.height=height;
  }
  public void show(){
    System.out.println("(x,y)=("+x+","+y+")");
    System.out.println("(幅,高さ)=("+width+","+height+")");
    System.out.println("面積="+width*height);
  }
  public static void main(String[] args){
    Rect r=new Rect(6,4,3,2);
    r.show();
  }
}


○実行結果

D:\atsushi\Java\P110>java Rect
(x,y)=(6,4)
(幅,高さ)=(3,2)
面積=6
-- Press any key to exit (Input "c" to continue) --

○解説

例題14-2と同様にインタフェースを実装していますが、その処理内容は異なります。
インタフェースは共通の雛形を提供するのが目的で、その処理内容は実装クラスに委ねるのでしたね。

ところで、Circle クラスと Rect クラスは階層が違います
(インタフェースはクラス階層に含まれません)。

Object
 └- Circle

Object
 └- Point
   └- Rect

それでも、同様のメソッド show で、インスタンスの情報を表示することは同じです。
インタフェースを実装することで同じ操作性が得られたのですね。

スーパークラスにインタフェースと同じメソッドがあったら?

interface Interface{
  void show();
}

class Super{
  public void show(){ }
}

class Sub extends Super implements Interface{
  public static void main(String[] args){ }
}


これもまた、実装したと見なされます
コンパイルしてみても何のエラーも検出されません。
もし仮に、実装したと見なされないのであれば、
Sub クラスが abstract じゃないことに文句を言ってくるはずですよね。

D:\atsushi\Java\P110-2>java Sub
-- Press any key to exit (Input "c" to continue) --

例題14-4 インタフェース型の変数を宣言する。

○プログラム

interface Math{
  double PI=3.14;
  void show();
}

class Circle implements Math{
  double r;
  public Circle(double r){
    this.r=r;
  }
  public void show(){
    System.out.println("面積="+PI*r*r);
  }
  public static void main(String[] args){
    Math m=new Circle(12.3);
    System.out.println("円周率="+PI);
    System.out.println("半径="+((Circle)m).r);
    m.show();
  }
}


○実行結果

D:\atsushi\Java\P114>java Circle
円周率=3.14
半径=12.3
面積=475.0506000000001
-- Press any key to exit (Input "c" to continue) --

○解説

インタフェースはインスタンスを作ることが出来ません。
つまり、Math m=new Math(); はコンパイルエラーです。
しかし、インタフェースの変数にそれを実装したクラスのインスタンス(のポインタ)を代入することは可能です。
これは多態性でやりましたね。

17. System.out.println("半径="+((Circle)m).r);

r は Circle クラスのフィールドなので、
通常は Math インタフェースの変数 m からアクセスすることは出来ません。
しかし、m には Circle クラスのインスタンス(のポインタ)が代入されています。
したがって、キャストすれば、Circle クラスにもアクセスすることが出来ます。

18. m.show();

show は Math インタフェースの(抽象)メソッドです。
したがって、Math インタフェースの変数 m からアクセスできます。
ただし、オーバーライドされているので、実際に呼び出されるのは Circle クラスの show メソッドです。

ここでも重要なのは、同じ操作性が提供されているということです。
とりあえず show メソッドを呼び出せば、インスタンスの情報を表示させることが出来ます。

例題14-3の Rect クラスも Math インタフェースを実装しているので、
Math m=new Rect(6,4,3,2); とすることが出来ます。

また、インタフェースは配列を作ることが出来ます。

Math[] m=new Math[2];
m[0]=new Circle(12.3);
m[1]=new Rect(6,4,3,2);

for(int i=0;i<m.lengh;i++){
  m[i].show();
}


こうすれば、同じ操作を繰り返す(ループ)だけで、
すべてのインスタンスの情報を表示させることが出来ます。

○補足

Math m=new Circle(12.3);
System.out.println("半径="+((Circle)m).r);


このようなキャストが許されるのは、Math インタフェースの変数 m に、
それを実装するクラス Circle のインスタンス(のポインタ)が代入されているからです。
もし仮にそうでないのならば、ClassCastException という例外がスローされます。

例題14-5 引数がインタフェースのメソッド。

○プログラム

interface Math{
  double PI=3.14;
  void show();
}

class Circle implements Math{
  double r;
  public Circle(double r){
    this.r=r;
  }
  public void show(){
    System.out.println("面積="+PI*r*r);
  }
  static void func(Math m){
    System.out.println("円周率="+PI);
    System.out.println("半径="+((Circle)m).r);
    m.show();
  }
  public static void main(String[] args){
    Circle c=new Circle(12.3);
    func(c);
  }
}


○実行結果

D:\atsushi\Java\P116>java Circle
円周率=3.14
半径=12.3
面積=475.0506000000001
-- Press any key to exit (Input "c" to continue) --

○解説

メソッドの引数としてインタフェースを使うことも出来ます。

Circle c=new Circle(12.3);
func(c);


は、

Math m=new Circle(12.3);
func(m);


としても同じです。

例題14-6 インタフェースを継承して拡張したインタフェース。

○プログラム

interface Math{
  double PI=3.14;
  void show();
}

interface Physics extends Math{
  double G=9.8;
}

class High implements Physics{
  double time;
  public High(double time){
    this.time=time;
  }
  public void show(){
    System.out.println("落下高度="+0.5*G*time*time);
  }
  public static void main(String[] args){
    High h=new High(5.43);
    System.out.println("重力加速度="+G);
    System.out.println("落下時間="+h.time);
    h.show();
  }
}


○実行結果

D:\atsushi\Java\P119>java High
重力加速度=9.8
落下時間=5.43
落下高度=144.47601
-- Press any key to exit (Input "c" to continue) --

○解説

インタフェースを継承して出来るのは、やっぱりインタフェースです。
class High extends Physics はコンパイルエラーです。

Math は Physics のスーパーインタフェースです。
Physics は Math のサブインタフェースです。

○補足

クラスの継承においてスーパークラスは一つだけしか指定できませんが、
インタフェースの継承においては、スーパーインタフェースは複数指定可能です。
詳しくは例題14-7で解説します。

例題14-7 インタフェースを多重実装する。

○プログラム

interface Math{
  double PI=3.14;
  void show();
}

interface Physics{
  double G=9.8;
}

class High implements Math,Physics{
  double time;
  public High(double time){
    this.time=time;
  }
  public void show(){
    System.out.println("落下高度="+0.5*G*time*time);
  }
  public static void main(String[] args){
    High h=new High(5.43);
    System.out.println("重力加速度="+G);
    System.out.println("落下時間="+h.time);
    h.show();
  }
}


○実行結果

D:\atsushi\Java\P120>java High
重力加速度=9.8
落下時間=5.43
落下高度=144.47601
-- Press any key to exit (Input "c" to continue) --

○解説

implements の後に , で区切ってインタフェースを並べれば、
いくらでもインタフェースを多重実装(こんな言葉はないかも)することが出来ます。

Java言語はクラスを多重継承することが出来ません。
段階的に継承することは出来ても、スーパークラスは必ず一つと決まっています。
コレ!と根本になるクラスを決めたら、後は自分で地道に拡張していくしかないのです。
途中で、違うクラスを継承したくなっても不可能なのです。
C++言語では多重継承できたのに、不便ですね。
しかし、インタフェースを使えば擬似的に多重継承することが出来ます。
ただし、インタフェースを実装するのは自分ですから、
既存のメソッドの恩恵を受けることは出来ません。
あまり意味がないような気がするのは私だけ……?

しかし、最初にも言いましたが、
インタフェースはそれを実装したクラスに対して共通の雛形、操作性を提供することを目的としています。
つまり、共通のフィールド、メソッドを持っているということをプログラムで示すことに意味があるのです。
多態性も重要です。
操作性は共通ですが、それを実装するクラスによって様々に振る舞います。
抽象クラスやインタフェースを上手く使えたなら
多態性がその実力を発揮できるかも知れませんね。

クラスとインタフェースの比較

クラス インタフェース
インスタンス 作れる 作れない
メソッド いろいろ 必ず public abstract
フィールド いろいろ 必ず public static final
スーパークラス 1つだけ 持てない
スーパーインタフェース 複数指定可能
( implementgs を使う)
 class A implements B,C{ 
  ……
 }
複数指定可能
( extends を使う)
 interface X extends Y,Z{ 
  ……
 }

戻る / ホーム