インターフェース・抽象クラス・ポリモーフィズム

■インターフェース・抽象クラス・ポリモーフィズム


はじめに


他のカリキュラムの内容と比較すると、ちょっと異色の章です。
情報自体はネットに溢れかえっていますが、
Javaを知っている前提の内容が多く書きっぷりがシンプルであるため、具体的なイメージが浮かびづらいです。
そのため、今までの切り口とは少々異なる角度から説明をしていきます。

また、この章においては課題提出はありません。
しかし、知識を深めてもらうために、 内容を読み進めつつ同時に実装してみることを推奨 します。

掘り下げると奥が深い内容になりますので、
この章に関しては書き方ももちろんそうですが、
特に 使用時のイメージ に重きを置いて学習していきましょう!

Step1: インタフェース


まずは英語の意味

→(AとBとの)接点、境界面

インターフェースの使用例


例1) UI(ユーザーインターフェース
→今、眼前にいるPCと、この文章を見せてくれてるディスプレイとか!
例2) API(Application Programing Interface
この業界では必ずと言っていいほど耳にするワードになります!
詳しくは記述しませんが、以下にフリマアプリでのAPIの例をあげますので、イメージを掴んでみてください。

  • ユーザー:検索ワード = 「◯◯ブランド」「ジャケット」を入力して、検索ボタンをターーーップ!
  • アプリ:ユーザーからお願いきた!検索ワードに合った服を探さなきゃ!でも、
    そのデータ自体は持ってないから、APIにお願いして取ってきてもらおう!
  • API:サーバー側にあるデータベースに、アプリさんから貰った
    「◯◯ブランド」「ジャケット」って情報と一致するデータがあったから渡すね!
  • アプリ:お!APIさっすが!ユーザーに見せてあげなきゃ!
  • ユーザー:お!これこれ!探してたやつ!

上記のユーザーとアプリを繋ぐ架け橋となってくれているものがAPIになります!
ここでのAPIのお仕事は アプリからのリクエストを受け取って、
受け取ったリクエストに合った内容をレスポンスする
 ということになっています!
ユーザーとアプリの境界に立って、お仕事してくれていますね(^^)

Javaにおけるインターフェース
前置きが長くなりましたが、ここからが本番です!

一番の特徴は、 インターフェースの実装対象への依存度が低く、 変更に強いプログラムを構築できる という点です!

その他、細かな特徴をいくつかあげます。

  • 処理無しのメソッド を記述したクラス
  • 実装先のクラスは 必ず インタフェースで宣言されているメソッドをオーバーライドし、
    オーバーライドしたメソッドの実際の処理を記述
  • 実装先のクラスはインターフェースに 制御(コントロール)される意味合いを含む
  • 実装先のクラスはインターフェースを 複数 実装できる
  • 使用目的を明確にて実装内容を絞らないと、ただの お邪魔虫

なかなか一回で理解するには難しそうです。

ですので、以下に、 現実世界での具体例 を交えた内容でコーディングしていきたいと思います!
【上記特徴を踏まえた具体例】
〜七海 & 上長もときさんと、愉快な仲間たちのお話〜

  1. インターフェース(=上長の司令もときさん)
  2. 1の司令をこなす側の七海
  3. 毎月訪れる現実世界の出来事(※1, 2 を使ってみる)
  4. もときさんの司令にも慣れてきたので師範代ゆうきさんの司令もこなしてみる

サンプル: OrderFromMotoki

インターフェースを作成する際は、通常のクラス作成とは異なった作成方法を取るため、
大まかな手順となる画面を提示します。

【当該処理のインタフェースの作成】

■ 作成したいフォルダやパッケージなどでメニューを開き、[新規] → [インターフェース] を選択

■ 名前(青枠)を入力し、完了ボタンを押下

また、必須ではありませんが、
「コメントの生成」にチェックを入れると作成時に開発者に優しい開発補助のコメントを自動挿入してくれますので、
必要な場合はチェックを入れておきましょう。

■ インタフェースに持たせたい機能を実装

ここからインターフェースにメソッドを実装します。

インタフェースには、そのインタフェースに持たせたい機能(処理やタスク)に適した内容を記述していきます。

【OrderFromMotoki.java】

/**
 * 1.インターフェース: 上長(もときさん)の司令
 * <pre>public interface インターフェース名 {}<pre>
 */
public interface OrderFromMotoki {

    /* ----- インターフェースメソッドの {} の処理は何も書かない! ----- */

    // 司令(月末までに勤務表出せや!
    public void daseyaKinmuhyo();

    // 司令(できれば月末までに交通費出せや!
    public void daseyaKotsuhi();

    // お邪魔虫な司令
    // 例)セブンイレブン行けや!
    // (こじつけてしまえば何でも有りですが、必ずこなす業務的な司令としては、なんか適していないよねって話
    public void goToSevenEleven();
}

解説

・ 必要な 機能(処理やタスク)の実装
インタフェースの内容に適したメソッドを定義していきます。
今回は必要機能に加えて、あえて お邪魔虫な司令 を加えています。

また、インターフェースのアクセス修飾子には
基本的に public を指定します。
protected などが指定されるとコンパイルエラーとなります。
(それぞれ適した使用ケースがあるのですが、今回は汎用的な public で実装していきます。

【インタフェースの実装】

続いてインターフェースを implements (実装)していきます。

事前にインターフェースの実装対象となるクラスを作成しておきましょう。
(今回は Nanaumi.java クラスとします。

■ インターフェースをクラスに実装

クラス名 implements インターフェース名 のように記述します。

自動作成された直後の状態では、
実装したインターフェースのメソッドがクラスに存在しないことによるコンパイルエラー が表示されますので対応していきます。

エラーアイコン、または、下線が表示されているインターフェースより解決するのですが、

今回はエラーアイコンより解決していきます。

■ エラーアイコンより、[エラーアイコンをクリック] → [インターフェース名をインポートしますをクリック] (これに関しては、imoprtが済んだ状態であればスキップして構いません。

■ エラーアイコンより、[エラーアイコンをクリック] → [実装されていないメソッドの実行]

インターフェースに定義されている機能(定義した3つのメソッド)を実装します。
(画像の右側の小窓に表示されている内容になります。

以下のように、手書きではなくEclipseにお任せすると、コメント( TODO 部分)を自動生成してくれます。
「メソッドは実装しておいたから、後は好きなように必要な処理を実装してね!」という意味になります。

見慣れない@Overrideがあるかと思います。
これは アノテーション と呼ばれ、コンパイラで出力される警告メッセージを抑制したり、
実行環境によってプログラムの動作を変更したりできます。

アノテーションには複数の種類があり自作することも可能ですが、ここでは、カリキュラムで出てくる
@Override についてのみ知っておきましょう!

わかりやすい記事があったので、こちらをご覧ください!
アノテーションについて

さて、インターフェースの実装も完了したので、

ここからはコーディング内容を見ていきましょう。

【Nanaumi.java】

/**
 * 2. 司令をこなす側: 七海クラス
 * <pre>public interface インターフェース名 {}<pre>
 */
public class Nanaumi implements OrderFromMotoki {

    private String name;
    private String date;

    public Nanaumi(String n, String yyyyMM) {
        name = n;
        date = yyyyMM;
    }

    /* 
     * 司令(インターフェース)が増えた際にOverrideした各メソッドそれぞれを修正することになると、
     * 修正箇所を探すことは大変だし、賢くない書き方になるので一箇所にまとめましょう!
     * 以下はOverrideしたメソッドの処理を実行する際に、フラグを立てて該当す処理をさせるメソッドの例
     */
    // 司令をこなす!
    private void submitOrder(final int shoriFlg) {
        String nameAnd = name + "、";

        if (shoriFlg == 0) {
            // 勤務表
            System.out.println(nameAnd + date + "の勤務表出しました(`・ω・´)ゞ!!!!!!");
        } else if (shoriFlg == 1) {
            // 交通費
            System.out.println(nameAnd + date + "の交通費も出しました(`・ω・´)ゞ!!!!!!");
        } else {
            // その他
            System.out.println(nameAnd + "本当はまだ何も出してません(`・ω・´)ドヤ!!!!!!");
        }
    }

    // 言いづらいけど、何もしてません!←
    public void doNothing() {
        submitOrder(-1);
    }

    /* ----- インターフェースのメソッドを Override して {} 内に 処理を記述! ----- */

    @Override
    // 勤務表出せや!: OrderFromMotoki
    public void daseyaKinmuhyo() {
        submitOrder(0);
    }

    @Override
    // できれば交通費も出せや!: OrderFromMotoki
    public void daseyaKotsuhi() {
        submitOrder(1);
    }

    @Override
    // セブンイレブン行けや!: OrderFromMotokiのお邪魔虫メソッド
    // (わざわざsubmitOrder(int)で処理させる必要もないよね!
    public void goToSevenEleven() {
        System.out.println("先にセブンイレブン行ってきやす!");
    }
}

【NanaumiMain.java】

/**
 * 3. 七海クラス: メイン処理
 * <pre>インターフェースを実装したクラスのインスタンス生成してメソッドを呼び出す<pre>
 */
public class NanaumiMain {

    public static void main(String[] args) {
        // 3. 現実(1, 2 を使ってみる)
        Nanaumi Nanaumi = new Nanaumi("七海", "2019/03");
        Nanaumi.daseyaKinmuhyo();
        Nanaumi.daseyaKotsuhi();
        Nanaumi.doNothing();
        Nanaumi.goToSevenEleven();
    }
}

【実行結果】

七海、2019/03の勤務表出しました(`・ω・´)ゞ!!!!!!
七海、2019/03の交通費も出しました(`・ω・´)ゞ!!!!!!
七海、本当はまだ何も出してません(`・ω・´)ドヤ!!!!!!
先にセブンイレブン行ってきやす!

解説

・ インターフェースにより実装したメソッドの処理のみを実装
大本の司令(勤務表提出・交通費提出・おじゃま虫な司令)は、クラス内で定義した訳ではないため、
単純に その中身の処理のみを実装することで 司令に対しての挙動を表現できます。

つまるところ、

  • 司令(インターフェースメソッド)が増えれば、 司令(インターフェースメソッド)を新たに実装し処理を記述
  • こなす必要の無い司令となったものに関しては、 関連する処理を削除するだけで済む

という訳です!

もちろん、インターフェースの司令を自身のクラス内で定義して実装することでもやりたいことは実現できるのですが、
複数人で共同開発をしていく上では、修正箇所が増えることは作業効率の低下に直結してしまいますし、
修正ミスや修正漏れの危険性も鑑みなくてはなりません。
更に言えば、その修正範囲の試験も実施する必要が出てきます。

開発者側の都合ではあるのですが、

そういった 悩ましい局面を軽減してくれる開発の仕方 になります!

サンプル: OrderFromShihandai
インターフェースの実装は複数可能というところで、もう一つインターフェースを作成しています。

■ 複数のインターフェースを実装

,(カンマ) 区切りで記述してきます。

※importが必要な場合はimportしましょう

【OrderFromShihandai.java】

// 4. 師範代ゆうきさんからの司令
public interface OrderFromShihandai {

    // Javaカリキュラムを作れ!
    public String doCreateJavaCurriculum();
}

【Nanaumi.java】

/**
 * 2. 司令される側 七海クラス
 * <pre>インターフェースを複数実装する<pre>
 */
public class Nanaumi implements OrderFromMotoki, OrderFromShihandai {

    // ... もときさんの司令とか
    
    @Override
    // Javaカリキュラム作れ!: OrderFromShihandai
    public String doCreateJavaCurriculum() {
        return "Javaカリキュラム完成しました!";
    }
}

【NanaumiMain.java】

// メイン: 七海クラス
public class NanaumiMain {

    public static void main(String[] args) {
        // 3. 現実(1, 2 を使ってみる)
        Nanaumi nanaumi = new Nanaumi("七海", "2019/01");

        // Javaカリキュラム作って報告
        final String message = nanaumi.doCreateJavaCurriculum();
        Sysytem.out.println("message = " + message);
    }
}

【実行結果】

message = Javaカリキュラム完成しました!

実装手順は難しくありません。

ここまで読み進めた上で、再度【インターフェースの特徴】を確認してみましょう!
ほんのりと理解が深まるかと思います!

Step2: 抽象クラス


続いて抽象クラスについて。
インターフェースと同様にまずは特徴を見ていきましょう!

  • インタフェースと通常のクラスの中間 のようなクラス
  • 通常のクラスと同様に、 フィールド や コンストラクタ 、 メソッド などの記述が可能
  • 【抽象メソッド】という、 処理無しのメソッド の記述が可能
  • 「継承」と同様に、複数の抽象クラスは継承不可
  • 抽象クラス内の abstract が付いたメソッド(抽象メソッド)は、
    この抽象クラスを継承するクラスでのオーバーライドと中身の処理の実装が必須
  • 必ず サブクラスのインスタンスを生成して使用する

はい、、、やはり難しいですね。

悩むよりまずは具体例見ていきましょう!
どんな時に使うか?

  • 絶対的な決まりごと・ルールなどを正確に実施させたい場合
  • 多重継承させたくない (複数の余計な司令をさせたくない)場合

使用時のイメージ
研修生に関して、半年の間はプログラミング研修を行いますよね?
例外はありますが、基本的には 決まりごとで、みんなが通る道です。
そういった ルール を設けています。

そして、現時点でJavaを学んでいる研修生には、今はPHPをやらせたくはありません。
(=ひとつだけのことに集中させたい訳です!

もちろん、スキルチェックを合格して、SESに切り替え後はOKです!
もときさんと、ゆうきさんの司令を受けることができます。
(=インターフェースを実装してもOKという訳です!

【上記特徴を踏まえた具体例】
〜カリキュラムをこなしてる研修生(そこのあなた!)
  1. 抽象クラス(= Javaのカリキュラム研修生クラス)
  2. 1のJavaのカリキュラム研修生を継承する研修生サブクラス
  3. 2の研修生クラスがやることは一体何だ?!(1のメソッドを使って確かめる!)

インターフェース同様、大まかな手順となる画面を提示します。

 

■ 抽象クラス(abstract)の作成

「abstract」のチェックボックスにチェックを入れて作成します。
(※もしチェックをし忘れても、コード内で「 abstract 」と記述すれば問題ありません。

作成時は特にエラーは発生しません。

■ 抽象クラス(abstract)の実装

抽象クラスに持たせたい機能を実装していきます。

【JavaCurriculumTrainee.java】

// 抽象クラス: Javaのカリキュラムだけしかこなせないクラス
// public abstract class XXXXX {}
public abstract class JavaCurriculumTrainee {

    private String name;

    /**
     * コンストラクタ
     * @param name
     */
    public JavaCurriculumTrainee(String name) {
        this.name = name;
    }

    /**
     * 抽象メソッド: Javaカリキュラムをこなす!
     * public abstract void xxxxx();
     */
    public abstract void doJavaCurriculum();

    /**
     * @return name
     */
    protected String getName() {
        return name;
    }

}

■ 抽象クラスをクラスへ継承

抽象クラスに定義されている機能(定義した1つのメソッド)を継承していきます。

通常の継承( extends )と同様、 クラス名 extends 抽象クラス と記述します。
(今回は Trainee クラスを対象とします。

まずは通常のクラス作成より、Traineeクラスを作成しましょう。
その後、作成した抽象クラスを継承させます。

■ エラーアイコンより、[エラーアイコンをクリック] → [実装されていないメソッドの実行]

■ エラーアイコンより、[エラーアイコンをクリック] → [コンストラクター ‘Trainee(String)’ を追加します]

抽象クラスで実装したコンストラクターも実装していきます。

またまたEclipseへお任せします。

クラスへ抽象クラスの継承が完了しました!

ここからはコードの内容をみていきます。

【Trainee.java】

// 「Javaのカリキュラムだけこなせよ!って決まりごとがある抽象クラス」を継承した研修生クラス
public class Trainee extends JavaCurriculumTrainee {

    public Trainee(String name) {
        super(name);
    }

    @Override
    public void doJavaCurriculum() {
        System.out.println("わたくし、" + super.getName() + "は、Javaカリキュラムをこなします(`・ω・´)ゞ!");
    }

}

【TraineeMain.java】

// メイン: 研修生クラス
public class TraineeMain {

    public static void main(String[] args) {
        Trainee trainee = new Trainee("七海");
        trainee.doJavaCurriculum();
    }
}

【実行結果】

わたくし、七海は、Javaカリキュラムをこなします(`・ω・´)ゞ!

解説

・ abstract の継承対象は、類似性の高いクラス
研修生には固定の作業(何かの言語のカリキュラム)をこなしてもらいます。
サンプルの抽象クラスでは 研修生(Trainee)に対して継承させました。
しかし、必要であれば研修後の社員、もしくは、内勤( Trainee 以外)にもカリキュラム自体をこなしてもらうことことは可能です。

類似 という観点で言えば、
研修生(大きなくくりで「社員」)には名前がありますよね。
社員には共通した情報(名前や年齢、所属)があり、クラスで言うところのフィールドとしての情報となります。

そういった、 「 インターフェースが持てない情報を扱える 」という点において、
似たような機能を持つインターフェースと抽象クラスを差別化することが可能です!

また、初めに 多重継承させたくない場合 と説明しましたが、
基本的に継承に関しては、 インターフェースのように複数の実装はできません。
「 継承 」と言う方法を選ぶこと、また、そのスーパークラスが 「 抽象クラス」であること で、
「多重継承を認めずに必須となる処理を実装させる」という開発方法が取れる訳です!

インターフェースと違い、 相手(処理を持たせたいクラス)を選ぶ書き方 にはなってしまいますが、
特定の大きな範囲で必須となる情報や処理を実装したい場合には安定した書き方になります!

Step3: ポリモーフィズム


大詰めです。
今までの「 インターフェース 」、「 抽象クラス 」は
ポリモーフィズムの概念を理解するために学んできたと言っても過言ではありません!

Step1: 概念を知る

・英語の意味
多態性、多相性、多様性
・Javaでの意味
「1つのスーパークラス(インタフェース)、複数のサブクラス(実装クラス)」を表現するときに起こり得る現象のこと
Step2: 使い方を知る
やや具体性に欠けますのでサンプルを提示します。まずは以下の下準備を行います。

  1. アクセス修飾子が protected or public のメソッドを持つスーパークラスを作成
    1-1. オーバーライドを強制したいので、 abstract で作成
  2. スーパークラスを継承し、スーパークラスのメソッドがオーバーライドされているサブクラスを作成
    2-1. サブクラスは 2つ以上 作成(※ 1つのみの場合は、ポリモーフィズムとなりません )
    2-2. スーパークラスクラスのメソッドの処理に、サブクラスから呼び出されたことがわかる処理を実装
    2-3. オーバーライドしたメソッド内の処理として、そのサブクラス専用の処理を実装
  3. スーパークラスを変数定義時のクラス(型)とし、 new する対象はサブクラスとする

今回は 2-2 のパターンで実装していきます。

すごく簡単にポリモーフィズムが確認できるサンプルを提示します!

【サンプル】

// スーパークラス
abstract class PolimoSuper {

    protected void call() {
        // 2-2 の要件を満たす処理
        System.out.println(this.getClass());
    }
}

// サブクラス1
class PolimoSub1 extends PolimoSuper {

    @Override
    protected void call() {
        super.call();
    }
}

// サブクラス2
class PolimoSub2 extends PolimoSuper {

    @Override
    protected void call() {
        super.call();
    }
}

...

// 呼び出し

// サブクラス1でインスタンスを生成
PolimoSuper polimo1 = new PolimoSub1();
polimo1.call();
// サブクラス2でインスタンスを生成
PolimoSuper polimo2 = new PolimoSub2();
polimo2.call();

【出力結果】

class study.PolimoSub1
class study.PolimoSub2

今回は study パッケージで作成したクラスであるため、
getClass() メソッドの取得結果にパッケージ名が付与されています。

見た通りですが、 インスタンスを生成するサブクラスに合ったクラス が出力されています。

少しイメージできたでしょうか?

では、更なる理解のためにとあるゲームのキャラクターをイメージしてみてください!
ピンク色まんまるい体、敵を吸い込んで、吸い込んだ敵の能力を使うことのできる大人気ゲームのキャラクターといえば皆さんはイメージ出来ましたね!?

このキャラクターの、敵を吸い込んで、吸い込んだ敵の能力を使うというこの一連の流れを作ってみましょう!
サンプル: ポリモーフィズム
【手順】

  1. ポリモーフィズムのベースとなる AbilityOfEnemy を継承した敵クラス の作成
  2. メインクラス KirbyMain でインスタンスを生成し、メソッドをコール

【AbilityOfEnemy.java】

// 抽象クラス: コピー対象の能力
public abstract class AbilityOfEnemy {

    // 敵の攻撃名(or 能力名称)
    protected String attackName;

    // 抽象メソッド: コピーした敵の能力を使う!
    protected abstract void useAbility();
}

【PlasmaWisp.java】

// サブクラス: プラズマ(電気飛ばしてくる青白い雲みたいなやつ!
public class PlasmaWisp extends AbilityOfEnemy {

    public PlasmaWisp(String attackName) {
        super.attackName = attackName;
    }

    @Override
    protected void useAbility() {
        System.out.println(super.attackName);
    }
}

【BradeKnight.java】

// サブクラス: ナイト(緑色の装甲の剣振り回してくるやつ!
public class BradeKnight extends AbilityOfEnemy {

    public BradeKnight(String attackName) {
        super.attackName = attackName;
    }

    @Override
    protected void useAbility() {
        System.out.println(super.attackName);
    }
}

【KirbyMain.java】

// メイン: 敵を吸い込んで、吸い込んだ敵の能力を使うシーン
public class KirbyMain {

    public static void main(String[] args) {
        // まずはプラズマを吸い込む!
        AbilityOfEnemy aoe = new PlasmaWisp("プラズマはどうだん!!!!");
        // 打つべし!
        aoe.useAbility();
        // 続いてナイトを吸い込む!
        aoe = new BradeKnight("ひゃくれつぎり!!!");
        // 切るべし!
        aoe.useAbility();
    }
}

【実行結果】

プラズマはどうだん!!!
ひゃくれつぎり!!!

解説

インスタンスを生成するクラスが変わることで、抽象クラスの参照変数の中身も変わる
敵のサブクラスが変わることで、キャラクターの持つ能力が変化します!

これが 「ポリモーフィズム」 現象です!
(無理やり感はありますが、イメージは大事です!
ポリモーフィズムの必要性について
ズバリ、
「共通のメソッドを呼び出すが、生成するオブジェクトによってその機能を変化させることが可能」 という点です!

ここで、あ!っとアハ体験できた方は、
ポリモーフィズムの 知識 が 理解 へと移行しているはずです!
自身持っていきましょう!

最後に


今回みてきた内容は数ある デザインパターン と呼ばれる開発技法の根幹を担う知識となります。
過去の偉人達が時間をかけ試行錯誤し生み出した、 開発者のための効率のよい開発方法 です。

Javaの基礎を理解していないと考え方が難しく馴染むまで時間がかかってしまう内容ではありますが、
これらの知識・技術の修得は、Java上級者への道程となります。
例題
上記に提示したキャラクターのプログラムを作成し、実行しましょう。
各クラスやインターフェイスは同一ファイルにまとめず、それぞれのファイルを作成してコンパイルしましょう!
(可能であれば、自身でテーマを考えてオリジナルのポリモーフィズム現象を再現してみるのもいいでしょう。

カテゴリー

アーカイブ

Close Bitnami banner
Bitnami