[Java] 12. インタフェース(interface)


Study / Java    作成日付 : 2019/08/20 23:46:23   修正日付 : 2021/01/12 18:13:52

こんにちは。明月です。


この投稿はJavaのインタフェース(interface)に関する説明です。


以前の投稿でクラスとクラスの継承に関して説明したことがあります。

link - [Java] 8. クラスの継承とthis、superキーワードの使い方


一応、継承の概念はクラスのすべての機能を続いてもらってクラスの機能を拡張することです。でもJavaでは複数継承、つまり、連関性がない二つのクラスを継承することができません。

多分、関数のあいまい点せいでできないと思います。 C/C++言語でよく発生した問題でしたが、Javaでは複数継承をできないようにして関数のあいまい点を初めからなくしたと思います。

// 親クラスAClass
class AClass {
  // 関数
  public void func() {
    // コンソール出力
    System.out.println("AClass func");
  }
}
// 親クラスBClass
class BClass {
  // 関数
  public void func() {
    // コンソール出力
    System.out.println("BClass func");
  }
}
// クラス、AClassとBClassを継承する。(実際にこんなに作成するとIDEツールからエラーが発生する。)
public class Example extends AClass, BClass {
  // 関数
  @Override
  public void func() {
    // 親クラスを呼び出す。
    super.func();
  }
}

上の例通りにEclipseに作成すればコンパイルエラーが発生します。

Exampleクラスを見ればAClassとBClassを継承してfunc関数を再定義(Override)しました。再定義までよいですが、func関数の中で親クラスのfunc関数を呼び出しますね。この時に親クラスの何処のfunc関数かというあいまい点があります。

C/C++では最後にローディングされた関数を呼び出します。でも、プログラム上でどっちが遅くローディングされるかを分からない問題があります。


この問題を解決するために、Javaからは複数継承をできないようにしたらしいです。


しかし、オブジェクト指向プログラミング(OOP)にきてオブジェクトの一番いい点がデータをクラス化して分類しながらリスト化にできることです。

つまり、AClassから継承したクラスははAClassグループ分類、BClassから継承したクラスははBClassグループに管理することができます。

import java.util.ArrayList;
import java.util.List;
// 親クラスAClass
class AClass {
  // 関数
  public void func() {
    // コンソール出力
    System.out.println("AClass func");
  }
}
// 親クラスBClass
class BClass {
  // 関数
  public void func() {
    // コンソール出力
    System.out.println("BClass func");
  }
}
// AClassを継承したクラス
class CClass extends AClass {
  // 関数
  @Override
  public void func() {
    // コンソール出力
    System.out.println("CClass func");
  }
}
// BClassを継承したクラス
class DClass extends BClass {
  // 関数
  @Override
  public void func() {
    // コンソール出力
    System.out.println("DClass func");
  }
}
// 実行クラス
public class Example {
  // 実行関数
  public static void main(String... args) {
    // AClassリスト
    List<AClass> aClassList = new ArrayList<>();
    // BClassリスト
    List<BClass> bClassList = new ArrayList<>();
    // AClassリストにAClassのインスタンスを生成して格納する。
    aClassList.add(new AClass());
    // CClassはAClassクラスを継承したのでAClassタイプに宣言することができる。
    aClassList.add(new CClass());
    // BClassリストにBClassのインスタンスを生成して格納する。
    bClassList.add(new BClass());
    // DClassはCClassクラスを継承したのでCClassタイプに宣言することができる。
    bClassList.add(new DClass());
    // リストのアイテムを取得
    for (int i = 0; i < aClassList.size(); i++) {
      // アイテム取得
      AClass cls = aClassList.get(i);
      // コンソール出力
      cls.func();
    }
    // リストのアイテムを取得
    for (int i = 0; i < bClassList.size(); i++) {
      // アイテム取得
      BClass cls = bClassList.get(i);
      // コンソール出力
      cls.func();
    }
  }
}


まず、理解することはAClassとCClass間の関係です。CClassはAClassを継承しました。以前、StackとHeapの関係に関して説明したことがあります。

link - [Java] 10. メモリの割り当て(stackメモリとheapメモリ、そしてnew)とCall by reference(ポインタによる参照)

クラスのインスタンスを生成する時に変数タイプとインスタンスタイプは同じことにして実装します。

AClass a = new AClass();

AClass aはStackメモリに格納するし、new ACLassはHeapメモリに割り当てします。

実は変数のデータタイプを宣言する時に合わせる条件があります。

1.インスタンスと同じデータタイプならOK

2.クラスより親クラスやインターフェースならOK


つまり、CClassの場合はAClass c = new CClass();が可能だということです。理由はCClassはAClassの継承したので、AClassの機能とメモリ構造があります。

そのため、AClassで宣言して関数を呼び出しても問題ないです。(参考で、Javaのクラスは原始データタイプ以外はすべてObjectクラスを継承されたので、すべてのデータはObjectタイプで宣言できます。)

なので上の例通りにAClassとBClassをリストで区分することができます。


でも、AClassとDClassは関数構造は同じですが、AClass a = new DClass();はできません。

理由は構造が人が見て同じことで、実はAClassとDClassは連関性が全然ありません。もちろん、AClassとDClassはObjectから継承したので、Objectでは分類することができます。

でも、Objectタイプはfunc関数がないので、Object変数タイプではfunc関数を呼び出すことができません。

しかし、AClassとBClassは差異を置いてAClassとDClassを一つで分類したと思えばインターフェースを使えばできます。

import java.util.ArrayList;
import java.util.List;
// インターフェース
interface IClass {
  // func関数定義
  public void func();
}

// 親クラスAClass、インターフェースIClassを継承
class AClass implements IClass {
  // 関数
  public void func() {
    // コンソール出力
    System.out.println("AClass func");
  }
}

// 親クラスBClass
class BClass {
  // 関数
  public void func() {
    // コンソール出力
    System.out.println("BClass func");
  }
}

// AClassを継承したクラス
class CClass extends AClass {
  // 関数
  @Override
  public void func() {
    // コンソール出力
    System.out.println("CClass func");
  }
}

// インターフェースIClassを継承
// BClassを継承したクラス
class DClass extends BClass implements IClass {
  // 関数
  @Override
  public void func() {
    // コンソール出力
    System.out.println("DClass func");
  }
}

// 実行クラス
public class Example {
  public static void main(String... args) {
    // IClassクラスリスト
    List<IClass> IClassList = new ArrayList<>();
    // AClassインスタンスを生成
    IClassList.add(new AClass());
    // BClassインスタンスを生成
    IClassList.add(new CClass());
    // DClassインスタンスを生成
    IClassList.add(new DClass());
    // リストのアイテムを取得
    for (int i = 0; i < IClassList.size(); i++) {
      // アイテム取得
      IClass cls = IClassList.get(i);
      // コンソール出力
      cls.func();
    }
  }
}


上の例をみればDClassはAClassとIClassを継承しました。AClassはIClassを継承しました。

つまり、DClassとAClassはIClassを継承してIClassはfunc関数が定義されているので、リストからIClass定義してクラスを分類してインスタンスを格納することができます。

コンソール結果はAClassとBClass、CClass順番で表示します。


ここでインタフェースの特徴はnewでクラスを割り当て(インスタンス生成)をできないです。ただ、Stack領域の変数タイプとしてだけ使えます。

インタフェースはメンバー変数も設定することができないし、関数の場合も関数名まで定義することができます。heap領域、つまり実行コードを実装しません。


実行コードが実行されてないので、インタフェースは複数継承しても問題ないです。関数名だけで定義されているので何を実行するかをextendsで継承した親クラスか割り当ているクラスが実行されます。

インタフェースを継承したときにはimplementsキーワードを使います。一般的にクラス継承はextendsを使います。


また、ここでもう一つ知るものがあります。ListとArrayListクラスとLinkedListの関係です。

link - [Java] 5. 配列とリスト(List)、マップ(Map)の使い方

Listタイプはインスタンスです。ArrayListとLinkedListはクラスです。つまり、Listインタフェースで変数タイプを設定してArrayListのインスタンスを生成しました。


ここまでJavaのインタフェース(interface)に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

最新投稿