puzzle
@puzzle (kokoko)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

インターフェイスについて

Q&A

Javaのinterfaceについて

インターフェースを新しく学習しているところで、つまづいてしまう点が出てきました...
インターフェースを実装(implements)するということは、親クラスがインターフェースのクラスになるということなのでしょうか。さらに、実装したクラスを継承したクラスのインスタンスについて疑問に思う点があります。

A.java
interface A {
      public void do_somthing();
}
B.java
public class B implements A {
       ~~~~~~~~~~~~~
       ~~~~~~~~~~~~~
       @Override
       public void do_somthing();
}
C.java
public class C extends B {
       ~~~~~~~~~~~~~
       ~~~~~~~~~~~~~
       @Override
       public void do_somthing();
}
main.java
public class TestShape {
    public static void main(String[] args) {
        A a[] = {
            new B(), // BはAを実装
            new C()  // CはBを継承
        };
    //以下なんらかの処理
    }
}

interfaceというかなり抽象的なものに子クラス(?)のインスタンスを代入できるのがなんか納得いきません。
質問がよく分かりにくい形になってしまい申し訳ありません。
ご回答よろしくお願いします。

0

2Answer

まず質問の

インターフェースを実装(implements)するということは、親クラスがインターフェースのクラスになるということなのでしょうか。

については違います。
順を追って説明します。

インターフェースについて

インターフェースは名前の通り、クラスのインターフェースを定義するための物です。
これには具体的な実装方法は含みません。

プログラミング以外でもこの概念は使われていて、
たとえば標準的なマウスであれば、

  • カーソルの移動ができる
  • 左クリックができる
  • 右クリックができる
  • スクロールができる

というインターフェースを持っていることになります。
ソースに起こすならこんな感じですね。

interface Mouse {
    /**
     * カーソルを移動します。
     * @param x 左右の移動量
     * @param y 上下の移動量
     */
    public void move(Integer x, Integer y);

    /**
     * 左クリックを行います。
     */
    public void leftClick();

    /**
     * 右クリックを行います。
     */
    public void rightClick();

    /**
     * スクロールを行います。
     * @param amount スクロールする行数
     */
    public void scroll(Integer amount);
  }
}

マウスの中の具体的な実装(implement)はメーカーや商品によって異なりますが、
インターフェースが同じであるため、どのマウスでも同じように扱うことができますよね。
ソースに起こすならこんな感じですね。

/**
 * 普通のマウス
 */
class NormalMouse implements Mouse {
  public void move(Integer x, Integer y){
    // 赤外線を使ったりなんやかんや
  };
  
  // 他の実装は割愛
}

/**
 * 古いマウス
 * 
 * ※下に球が入っていてそれの転がりで移動を検出する!
 * ※球だから掃除しないと動きづらくなったりする…
 */
class OldMouse implements Mouse {
  public void move(Integer x, Integer y){
    // 球の転がりを検出したりなんやかんや
  };
  
  // 他の実装は割愛

  public void cleaning(){
    // ボールの掃除など…
  }
}

この時NormalMouseやOldMouseは、Mouseを実装しているだけで親クラスはありません。

中の仕組みが違ったり分からなかったりしても「マウス」というインターフェースを知っていればマウスは使えます。
インターフェースというのは、「こう動かすとこういう結果になりますよ。」という使い方だけを表しているわけですね。

そしてJavaではinterfaceをimplementするということは、「このクラスはこのinterfaceで使えますよ」ということを保証していることになります。
※interface内の要素を一つでも実装していないとコンパイルエラーになります。

ですので、「マウス使いたいからちょっと貸して!」と言っている人がいたとして、
普通のマウスを渡しても古いマウスを渡してもどちらでも問題ないわけです。
ソースに起こすならこんな感じですね。

public class UseMouseTest {
  public static void main(String[] args) {
      Mouse mouse = new NormalMouse();// new OldMouse(); でも問題ない!
      useMouse(mouse);
  }

  public useMouse(Mouse mouse){
    // 左クリックしたりなんやかんや…
    mouse.leftClick();
  }
}

またもし「古いマウスでもちゃんと動くか試したいから貸して!」と具体的な実装を指定している人であれば古いマウスを渡さなくてはいけません。

public class UseMouseTest {
  public static void main(String[] args) {
      OldMouse oldMouse = new OldMouse();// new NormalMouse(); ではだめ!
      oldMouseTest(oldMouse);
  }

  public oldMouseTest(OldMouse oldMouse){
    // 左クリックしたりなんやかんや…
    oldMouse.leftClick();
    // 掃除もする…
    oldMouse.cleaning();
  }
}

継承について

インターフェースの例を引き継いで説明します。
通常のマウスの機能に加えて、サイドボタンが追加されているマウスなどを作りたい場合もあります。
こういう場合は継承することで、追加の機能のみの開発で済ませることができます。
ソースに起こすならこんな感じですね。

/**
 * サイドボタン付きのマウス
 *
 * 普通のマウスの機能を持っている!
 * 戻る と 進む もできる!
 */
class SideButtonMouse extends NormalMouse {
  /**
   * 戻るボタン
   */
  public void back() {
    // 戻る
  }

  /**
   * 進むボタン
   */
  public void forward() {
    // 進む
  }
}

このときSideButtonMouseはNormalMouseのサブクラス(または子クラス、派生クラス)であり、
NormalMouseはSideButtonMouseのスーパークラス(または親クラス、基底クラス)です。

継承で守らないといけない大事なルールとして、リスコフの置換原則があります。
簡単に言うと「サブクラス is スーパークラス」です。
今回の例では「サイドボタン付きのマウス は 普通のマウス である」ですね。
サブクラスはスーパークラスとしても使えなければなりません。

そして
「SideButtonMouseはNormalMouseである」
「NormalMouseはMouseを実装している」
ということは、
「SideButtonMouseはMouseを実装している」
ということになります。

まとめ

ここまでのそれぞれのクラス・インターフェースを使って関数を作るとこんな感じです。

/**
 * マウスを使う
 * 
 * NormalMouse, OldMouse, SideButtonMouseすべてOK
 */
public useMouse(Mouse mouse){
  mouse.move(2, 3);
  mouse.leftClick();
  mouse.rightClick();
  mouse.scroll(2);
}

/**
 * 古いマウスを使う
 * 
 * OldMouseのみOK
 */
public useOldMouse(OldMouse mouse){
  mouse.move(2, 3);
  mouse.leftClick();
  mouse.rightClick();
  mouse.scroll(2);

  mouse.cleaning();
}

/**
 * 普通のマウスを使う
 * 
 * NormalMouse, SideBUttonMouseならOK
 */
public useNormalMouse(NormalMouse mouse){
  mouse.move(2, 3);
  mouse.leftClick();
  mouse.rightClick();
  mouse.scroll(2);
}

/**
 * サイドボタン付きのマウスを使う
 * 
 * SideBUttonMouseのみOK
 */
public useSideButtonMouse(SideButtonMouse mouse){
  mouse.move(2, 3);
  mouse.leftClick();
  mouse.rightClick();
  mouse.scroll(2);

  mouse.back();
  mouse.forward();
}

また上の例では「古いマウス」や「サイドボタン付きのマウス」はクラスとして記述しましたが、
実際はこれもインターフェースにして実装と分けほうが良かったりもします。

インターフェースでは他のインターフェースを継承することができますので、
このようにできます。

interface SideButtonMouse extends Mouse {
  /**
   * 戻るボタン
   */
  public void back();

  /**
   * 進むボタン
   */
  public void forward();
}

interface MySideButtonMouse extends NormalMouse implements SideButtonMouse {
  public void back() {
    // 戻る
  };

  public void forward() {
    // 進む
  }
}

こうすることで実装に影響されない「サイドボタン付きのマウス」を表現できます。

長々となってしまいましたが、参考になれば幸いです。

1Like

Comments

  1. @puzzle

    Questioner

    ご回答ありがとうございます!
    自分の中でスッキリすることができました。オブジェクト指向に触れ始めてからまだ日が浅く、色々なオプション(extendsやimplements等)がたくさん出てきて混乱してしまいます。。。
    もう一つ似ているオプションとしてabstractがありますが、大部分が完成しているクラスに未定のメソッドがある時に利用するのでしょうか?
  2. 最初はキーワードが多くて混乱しますよねー。わかります。
    英語が得意なら、日本人から見た「ちょっと難しい言葉も入ってるけど全部日本語の文章」のような感じでだいぶ楽なんでしょうが…。

    じつはあまりに長いと辛いかな~と思いabstractには触れていませんでした。
    さわりを理解しやすいように一部の機能を使う例で説明します。
    (実際には説明する他にもルールや使い方があります)

    abstractは日本語で「抽象」です。抽象的な物を表すための機能ですね。
    回答で例に出していたマウスでいうと、
    実際にはダブルクリックなどもありますよね。
    インターフェースに追加するとこうです。

    ```Java
    interface Mouse {
    public void move(Integer x, Integer y);

    public void leftClick();

    /**
    * ダブルクリックを行います。
    */
    public void doubleClick();

    public void rightClick();

    public void scroll(Integer amount);
    }
    ```

    ダブルクリックは『左クリックを素早く2回』なので、
    必ず左クリックがあるという前提のマウスにおいて、
    抽象として定義することができます。

    つまりこうなります。

    ```Java
    /**
    * マウスの抽象
    */
    abstract class AbstractMouse implements Mouse {
    /**
    * ダブルクリックを行います。
    */
    public void doubleClick() {
    // 左クリックを二回する
    leftClick();
    leftClick();
    };
    }
    ```

    これを実際に継承するとこんな感じです。

    ```Java
    /**
    * 普通のマウス
    */
    class NormalMouse extends AbstractMouse{
    public void move(Integer x, Integer y){
    // 割愛
    };
    public void leftClick(){
    // 割愛
    };
    public void rightClick(){
    // 割愛
    };
    public void scroll(Integer amount){
    // 割愛
    };
    }
    /**
    * 古いマウス
    */
    class OldMouse extends AbstractMouse{
    // 割愛
    }
    ```

    というように、抽象的な処理などを共通化する時に便利な機能です。
  3. @puzzle

    Questioner

    この例だと、leftClickをdoubleClickで使いたいけど、leftClickはinterfaceで定義したから具体的な処理は決まっていない-->abstractを使ってdoubleClickを書く、という流れでしょうか。
    例えば、abstractを書かずにそのままimplementsしてdoubleClickを実装しようとすると、不都合が起きてしまうのでしょうか。
    質問ばかりですみません。
  4. Mouseを実装するクラスはそれぞれdoubleClickを実装する必要がある
    →ただどんな実装方法でも「leftClickを2回」というのは同じ
    →共通化したい
    →抽象的なマウス(leftClickの仕組みは決まっていないがdoubleClickの仕組みは決まっている)として書きたい
    →abstractを使う
    という流れですかね。

    abstractを使わずそのままimplementして不都合が起きるとしたら、
    「doubleClickは間に0.1秒間隔を置く」という共通の仕様が0.2秒に変わった時にすべてのクラスに同じ変更を加える必要が出てくる、などですね。
    (※この例ではダブルクリックの仕様はすべてのマウスで同じと仮定しています)
  5. @puzzle

    Questioner

    なるほど!抽象的なマウスを書くという考えが私にはなかったです。確かに抽象的なマウスを作っておくことで、具体的にマウスを作る際に楽になりそうですね。個人的に感じたことですが、abstractを作る分一手間増えるように見えます。でもそれが後々、無駄に多くのコードを書かずに済むことにつながるのですかね。
    不都合の例を見ても、やはりこのようなオプションは使えるようになると便利なものなのですね。

javaはそこまで詳しくないのですが、多態性(ポリモーフィズム)というものですね。

インターフェースを実装(implements)するということは、親クラスがインターフェースのクラスになるということなのでしょうか。

インターフェースはクラスとは違うので、継承とは違います。

次のような例だともう少しイメージしやすいのではないでしょうか?

interface  {
    public void 歩く();
}

abstract class 生物 {
}

class 人間 extends 生物 implements  {
    public void 歩く() {
        // 2本足で歩く
    }
}

class  extends 生物 implements  {
    public void 歩く() {
        // 4本足で歩く
    }
}

class  extends 生物 {
}

public class Main {
    public static void main(String[] args) throws Exception {
         足を持った生物[] = {
            new 人間(),
            new (),
            new () // error 魚には足がない
        };

        // 足を持った生物配列のオブジェクトは、足インターフェースを実装しているので歩くことができる
        足を持った生物[0].歩く(); // 人間は足インタフェースがあるので歩ける
        足を持った生物[1].歩く(); // 犬も足インタフェースがあるので歩ける
    }
}
0Like

Comments

  1. @puzzle

    Questioner

    ご回答ありがとうございます!
    interfaceの中身を再現できるクラス(実装したクラス)のインスタンスを配列に代入できるという考え方でよろしいですか?
  2. そうですね。インターフェース型の変数(配列)は、そのインターフェースが実装されたオブジェクトを代入できる、ということになります。
  3. @puzzle

    Questioner

    オブジェクト指向の考え方をもっと勉強しなければですね。。。
  4. 基礎的な学習の範囲だと、クラスやインタフェースなどはピンとこない部分も多いと思います。
    サンプル程度の単純な処理を行なうには無くても困らなかったりするので。
    技術の習得において理解は後からついてくるようなものも多いので、必要に応じて深めていけば良いと思いますよ。
  5. @puzzle

    Questioner

    ありがとうございます、とても参考になります。考え方や特徴に慣れていこうと思います。

Your answer might help someone💌