Java超|12章のまとめ

抽象クラスで戦士の土台を整え、インターフェイスで能力の約束を結ぶ。12章は、Javaのクラス設計を「強い戦士チームの設計図」へ進化させる章です。

12章では、Javaのオブジェクト指向をさらに一歩進めるために、抽象クラスインターフェイス を学びました。

ここまで継承を学ぶと、親クラスから子クラスへ機能を受け継ぐ考え方が見えてきます。
しかし、実際のプログラム設計では、単に親子関係を作るだけでは足りない場面があります。

ドラゴンボール風にたとえると、戦士たちはそれぞれ違う個性を持っています。

戦士の種類個性
SuperSaiyanWarrior強力な気を解放して戦うサイヤ人系の戦士
FlameFighter炎の気を使って前線で戦う戦士
NamekianHealer神秘的な治療術で仲間を回復する戦士
ScoutWarrior索敵や周囲の状況確認を得意とする戦士

このように個性は違いますが、全員に共通するものもあります。

共通するもの内容
name戦士の名前
speed移動速度
show()自分の情報を表示する処理
move()移動する能力
useKi()気を使う能力

ここで大切になるのが、共通の土台能力の約束 を分けて考えることです。

抽象クラスは、まだ完成していないけれど、共通部分を持った土台を作るための仕組みです。
インターフェイスは、クラスの種類に関係なく「この能力を持つ」という約束を表す仕組みです。

12章では、抽象クラスとインターフェイスを使って、クラスを整理し、増やしやすくし、共通のルールで扱うための考え方を学びました。

抽象クラスは未完成の土台を作るための仕組み

抽象クラスは、普通のクラスのようにフィールドやメソッドを持てます。
しかし、普通のクラスと大きく違う点があります。

それは、抽象クラスそのものからはオブジェクトを作成できない という点です。

抽象クラスは完成品ではありません。
サブクラスのための共通の土台です。

ドラゴンボール風にたとえると、抽象クラス Warrior は、
「戦士なら最低限こういうものを持つ」
という共通設計図です。

Warrior に置くもの役割
name戦士の名前
speed移動速度
setSpeed()速度を設定する共通処理
showBaseInfo()名前や速度を表示する共通処理
show()子クラスで完成させる情報表示

戦士なら名前や速度を持つ、という点は共通です。
しかし、どんな情報を表示するかは戦士ごとに違います。

SuperSaiyanWarrior なら、気の型や覚醒状態を表示したいかもしれません。
FlameFighter なら、炎の技を表示したいかもしれません。
NamekianHealer なら、治療術や支援能力を表示したいかもしれません。

そこで抽象クラスでは、共通部分だけを用意し、クラスごとに違う部分はサブクラスに任せます。

抽象メソッドは必ず持つべき処理を宣言する

抽象クラスの中では、抽象メソッド を宣言できます。

抽象メソッドとは、処理内容を書かずに、メソッドの形だけを決めるメソッドです。

public abstract void show();

これは、
戦士なら show() を必ず持つ。ただし、表示内容は各サブクラスで決める
という意味です。

ドラゴンボール風にたとえると、戦士管理本部のルールとして、
「戦士は出撃前に自分の情報を表示できるようにする」
と決めているようなものです。

ただし、サイヤ人戦士、炎の戦士、治療戦士では、表示したい内容が違います。

クラスshow() で表示する内容
SuperSaiyanWarrior名前、速度、気の型
FlameFighter名前、速度、炎の技
NamekianHealer名前、速度、治療術

親クラスである Warrior は、show() が必要であることだけを決めます。
実際の中身は、それぞれの子クラスが完成させます。

これにより、親クラスは共通ルールを示し、子クラスは自分らしい処理を作れるようになります。

抽象クラスのオブジェクトを作成できない理由

抽象クラスは、直接 new でオブジェクトを作成できません。

Warrior warrior = new Warrior();

これはできません。

理由はシンプルです。
抽象クラスは未完成だからです。

ドラゴンボール風にたとえると、Warrior は「戦士」という共通概念です。
しかし、「戦士」という概念だけでは、実際に戦場へ出る人物にはなりません。

実際に動けるのは、次のような具体的なクラスです。

抽象的な土台具体的なクラス
WarriorSuperSaiyanWarrior
WarriorFlameFighter
WarriorNamekianHealer

抽象クラスは、半端なクラスではありません。
あえて未完成にしておくことで、共通部分を整理し、具体的な部分をサブクラスに任せるための設計道具です。

図:抽象クラスは戦士の共通土台を作る

この図が示していること

この図では、抽象クラス Warrior が戦士としての共通の土台を作っていることを表しています。

Warrior には name、speed、setSpeed()、showBaseInfo() のような共通部分があります。
一方で、show() は抽象メソッドとして残しておき、具体的な表示内容は子クラスで完成させます。

要素役割
Warrior共通の土台
abstract show()子クラスで完成させる必須処理
具体クラス自分らしい show() を作る

この図から分かることは、抽象クラスは「共通部分をまとめる場所」でありながら、「個別に変わる処理は子クラスへ任せる場所」でもあるということです。

インターフェイスは何ができるかの約束を表す

12章でもうひとつ大切なテーマが、インターフェイス です。

インターフェイスは、クラスと少し似ていますが、役割は違います。

抽象クラスは、共通の土台を表します。
インターフェイスは、何ができるか という能力の約束を表します。

ドラゴンボール風に考えると、戦士にはいろいろな能力があります。

能力インターフェイスの例意味
移動できるiMovablemove() を持つ
気を使えるiKiUseruseKi() を持つ
支援できるiSupportablesupport() を持つ
索敵できるiScouterUserscan() を持つ

ここで大切なのは、これらは「何者か」ではなく「何ができるか」を表している点です。

SuperSaiyanWarrior はサイヤ人系の戦士です。
NamekianHealer はナメック星の治療戦士です。

この2つは、クラスとしては違う存在です。
しかし、どちらも移動できるなら、iMovable を実装できます。
どちらも気を扱えるなら、iKiUser を実装できます。

クラス何者か何ができるか
SuperSaiyanWarriorサイヤ人系の戦士移動できる、気を使える、表示できる
FlameFighter炎の気を使う戦士移動できる、気を使える、表示できる
NamekianHealer治療戦士移動できる、支援できる、表示できる

このように、インターフェイスを使うと、クラスの種類をまたいで共通の能力を整理できます。

インターフェイスのフィールドは定数になる

インターフェイスにフィールドを書くと、それは基本的に定数として扱われます。

インターフェイスは、個々のオブジェクトの状態を管理する場所ではありません。
能力や約束に関する共通ルールを置く場所です。

ドラゴンボール風にたとえると、インターフェイスに書くフィールドは、個々の戦士の現在の体力や速度ではありません。

それよりも、戦士管理本部全体で共有する固定ルールのようなものです。

書く場所向いている内容
クラスのフィールド戦士ごとの名前、速度、状態
インターフェイスのフィールド固定された共通ルール、定数

たとえば、次のような値はインターフェイスに置く意味があります。

interface iMissionRule
{
    int MAX_MISSION_LEVEL = 10;
}

この MAX_MISSION_LEVEL は、変化する状態ではなく、固定された共通ルールとして扱います。

インターフェイスは、状態を管理するためのものではなく、能力やルールを表すためのものだと考えると整理しやすいです。

インターフェイスのメソッドは抽象メソッドになる

インターフェイスに書くメソッドは、基本的に抽象メソッドです。

つまり、処理の中身は書かずに、メソッドの形だけを決めます。

interface iMovable
{
    void move();
}

これは、
iMovable を実装するクラスは、move() を必ず持つ
という約束です。

実際の移動のしかたは、クラスごとに違ってかまいません。

クラスmove() の中身
SuperSaiyanWarrior舞空術で空中を素早く移動する
FlameFighter炎の勢いで前線へ踏み込む
NamekianHealer仲間の近くへ素早く移動する

インターフェイスは、メソッド名を並べるだけのものではありません。
クラスに共通の行動ルールを守らせるための仕組みです。

インターフェイスを実装すると多くのクラスをまとめて扱える

インターフェイスを使う大きな利点は、違うクラスを同じ型としてまとめて扱えることです。

たとえば、SuperSaiyanWarrior、FlameFighter、NamekianHealer がすべて iMovable を実装しているとします。

すると、次のように iMovable 型の配列でまとめられます。

iMovable[] members = new iMovable[3];

この配列には、iMovable を実装したクラスのオブジェクトを入れられます。

配列要素実体共通して呼び出せるメソッド
members[0]SuperSaiyanWarriormove()
members[1]FlameFightermove()
members[2]NamekianHealermove()

呼び出し側は、それぞれの具体的なクラス名を細かく気にしなくても、move() を呼び出せます。

for(int i = 0; i < members.length; i++){
    members[i].move();
}

同じ move() という呼び出しでも、実際には各クラスに合った動きが実行されます。

これが、インターフェイスと多態性の大きな力です。

図:抽象クラス型とインターフェイス型でまとめて扱う

この図が示していること

この図では、抽象クラス型とインターフェイス型で、異なる具体クラスをまとめて扱う様子を表しています。

左側の Warrior[] warriors では、SuperSaiyanWarrior、FlameFighter、NamekianHealer を戦士としてまとめています。
この場合、共通して show() を呼び出せます。

右側の iMovable[] members では、移動できるクラスをまとめています。
この場合、共通して move() を呼び出せます。

まとめ方呼び出せるメソッド見方
戦士としてまとめるWarrior[]show()共通の土台で見る
移動できる者としてまとめるiMovable[]move()共通の能力で見る

同じ show() や move() を呼び出しても、実際に動く処理はオブジェクトの実体によって変わります。

この図から分かることは、抽象クラスとインターフェイスを使うと、クラスの違いを保ったまま、共通の型で分かりやすくまとめられるということです。

スーパーインターフェイスを拡張してサブインターフェイスを作れる

インターフェイス同士にも親子関係を作れます。

インターフェイスが別のインターフェイスを受け継ぐときは、extends を使います。

interface iBattleFighter extends iMovable
{
    void attack();
}

この場合、iBattleFighter は iMovable の約束を受け継ぎます。
さらに、自分自身の約束として attack() を追加しています。

インターフェイス持っている約束
iMovablemove()
iBattleFightermove()、attack()

拡張される側をスーパーインターフェイス、拡張する側をサブインターフェイスと考えると分かりやすいです。

用語意味ドラゴンボール風のイメージ
スーパーインターフェイス基本となる能力の約束移動できる
サブインターフェイス基本能力を受け継ぎ、追加能力を持つ約束移動できて戦える

ドラゴンボール風にたとえると、まず「移動できる」という基本の能力があります。
その上に、「移動できるうえに攻撃もできる」という発展能力を作るイメージです。

この仕組みによって、能力の約束を段階的に整理できます。

抽象クラスとインターフェイスを使うと多態性で分かりやすいコードが書ける

12章全体を通して大切なのは、抽象クラスとインターフェイスを使うことで、多態性をより分かりやすく使えるようになる点です。

ドラゴンボール風に整理すると、次のようになります。

役割しくみ
戦士としての共通の土台抽象クラスWarrior
移動できる能力の約束インターフェイスiMovable
気を使える能力の約束インターフェイスiKiUser
実際の戦士具体クラスSuperSaiyanWarrior、FlameFighter、NamekianHealer

このように分けると、コードを書く側は、共通の型でまとめて扱いやすくなります。

Warrior 型なら、戦士としてまとめられます。
iMovable 型なら、移動できる者としてまとめられます。
iKiUser 型なら、気を使える者としてまとめられます。

まとめ方見方
戦士としてまとめるWarrior[]共通の土台で見る
移動できる者としてまとめるiMovable[]能力で見る
気を使える者としてまとめるiKiUser[]能力で見る

同じ命令でも、実際の処理はオブジェクトごとに変わります。

これが、共通のルールでまとめつつ、具体的な動きはクラスごとに変えられる というオブジェクト指向の強さです。

サンプルプログラム

ファイル名:Sample7.java

abstract class Warrior
{
    protected String name;
    protected int speed;

    public Warrior(String n)
    {
        name = n;
        speed = 0;
        System.out.println(name + "を戦士として登録しました。");
    }

    public void setSpeed(int s)
    {
        speed = s;
        System.out.println(name + "の速度を" + speed + "にしました。");
    }

    public void showBaseInfo()
    {
        System.out.println("戦士の名前は" + name + "です。");
        System.out.println("速度は" + speed + "です。");
    }

    public abstract void show();
}

interface iMovable
{
    void move();
}

interface iKiUser
{
    void useKi();
}

interface iBattleFighter extends iMovable
{
    void attack();
}

class SuperSaiyanWarrior extends Warrior implements iBattleFighter, iKiUser
{
    private String kiStyle;

    public SuperSaiyanWarrior(String n, String ks)
    {
        super(n);
        kiStyle = ks;
        System.out.println(name + " スーパーサイヤ人戦士を作成しました。");
    }

    public void move()
    {
        System.out.println(name + "は舞空術で空中を素早く移動します。");
    }

    public void useKi()
    {
        System.out.println(name + "は" + kiStyle + "の気を解放します。");
    }

    public void attack()
    {
        System.out.println(name + "は金色の気をまとって攻撃します。");
    }

    public void show()
    {
        showBaseInfo();
        System.out.println("役割はスーパーサイヤ人戦士です。");
        System.out.println("気の型は" + kiStyle + "です。");
    }
}

class FlameFighter extends Warrior implements iBattleFighter, iKiUser
{
    private String flameTechnique;

    public FlameFighter(String n, String ft)
    {
        super(n);
        flameTechnique = ft;
        System.out.println(name + " 炎の気を使う戦士を作成しました。");
    }

    public void move()
    {
        System.out.println(name + "は炎の勢いで前線へ踏み込みます。");
    }

    public void useKi()
    {
        System.out.println(name + "は炎の気を高めます。");
    }

    public void attack()
    {
        System.out.println(name + "は" + flameTechnique + "を放ちます。");
    }

    public void show()
    {
        showBaseInfo();
        System.out.println("役割は炎の気を使う戦士です。");
        System.out.println("炎の技は" + flameTechnique + "です。");
    }
}

class NamekianHealer extends Warrior implements iMovable
{
    private String healingSkill;

    public NamekianHealer(String n, String hs)
    {
        super(n);
        healingSkill = hs;
        System.out.println(name + " ナメック星の治療戦士を作成しました。");
    }

    public void move()
    {
        System.out.println(name + "は仲間の近くへ素早く移動します。");
    }

    public void show()
    {
        showBaseInfo();
        System.out.println("役割はナメック星の治療戦士です。");
        System.out.println("治療術は" + healingSkill + "です。");
    }
}

class Sample7
{
    public static void main(String[] args)
    {
        Warrior[] warriors = new Warrior[3];

        warriors[0] = new SuperSaiyanWarrior("蒼月", "蒼天気功");
        warriors[0].setSpeed(90);

        warriors[1] = new FlameFighter("炎牙", "紅蓮気弾");
        warriors[1].setSpeed(85);

        warriors[2] = new NamekianHealer("ネイロン", "光の再生術");
        warriors[2].setSpeed(60);

        System.out.println();

        for(int i = 0; i < warriors.length; i++){
            warriors[i].show();
            System.out.println();
        }

        iMovable[] movableMembers = new iMovable[3];

        movableMembers[0] = (iMovable)warriors[0];
        movableMembers[1] = (iMovable)warriors[1];
        movableMembers[2] = (iMovable)warriors[2];

        for(int i = 0; i < movableMembers.length; i++){
            movableMembers[i].move();
        }

        System.out.println();

        iBattleFighter[] battleMembers = new iBattleFighter[2];

        battleMembers[0] = (iBattleFighter)warriors[0];
        battleMembers[1] = (iBattleFighter)warriors[1];

        for(int i = 0; i < battleMembers.length; i++){
            battleMembers[i].attack();
        }
    }
}

実行結果

蒼月を戦士として登録しました。
蒼月 スーパーサイヤ人戦士を作成しました。
蒼月の速度を90にしました。
炎牙を戦士として登録しました。
炎牙 炎の気を使う戦士を作成しました。
炎牙の速度を85にしました。
ネイロンを戦士として登録しました。
ネイロン ナメック星の治療戦士を作成しました。
ネイロンの速度を60にしました。

戦士の名前は蒼月です。
速度は90です。
役割はスーパーサイヤ人戦士です。
気の型は蒼天気功です。

戦士の名前は炎牙です。
速度は85です。
役割は炎の気を使う戦士です。
炎の技は紅蓮気弾です。

戦士の名前はネイロンです。
速度は60です。
役割はナメック星の治療戦士です。
治療術は光の再生術です。

蒼月は舞空術で空中を素早く移動します。
炎牙は炎の勢いで前線へ踏み込みます。
ネイロンは仲間の近くへ素早く移動します。

蒼月は金色の気をまとって攻撃します。
炎牙は紅蓮気弾を放ちます。

サンプルプログラムで使っている設計

このサンプルプログラムでは、抽象クラスとインターフェイスを組み合わせています。

まず、Warrior 抽象クラスがあります。

Warrior は、戦士としての共通の土台です。

Warrior にあるもの役割
name戦士の名前
speed戦士の速度
setSpeed()速度を設定する共通処理
showBaseInfo()名前と速度を表示する共通処理
show()子クラスで完成させる抽象メソッド

次に、能力を表すインターフェイスがあります。

インターフェイス役割
iMovable移動できる
iKiUser気を使える
iBattleFighter移動できて、攻撃できる

iBattleFighter は iMovable を拡張しています。

interface iBattleFighter extends iMovable
{
    void attack();
}

つまり、iBattleFighter を実装するクラスは、attack() だけでなく、iMovable から受け継いだ move() も実装する必要があります。

インターフェイス持つ約束
iMovablemove()
iBattleFightermove()、attack()

このように、インターフェイスの拡張を使うと、能力の約束を段階的に整理できます。

大規模なプログラムでは抽象クラスとインターフェイスが特に重要になる

小さなプログラムでは、個別のクラスをそのまま使っても何とかなることがあります。

しかし、クラスが増えると、次のような設計が重要になります。

考えること理由
共通の土台をどこに置くか重複を減らすため
共通の能力をどう表すかまとめて扱いやすくするため
新しいクラスをどう追加するか拡張しやすくするため
どの型でまとめるか多態性を活かすため

抽象クラスを使えば、共通の状態や処理を整理できます。
インターフェイスを使えば、能力の約束を整理できます。
多態性を使えば、具体的なクラス名を細かく意識しすぎず、共通ルールで扱えます。

ドラゴンボール風にたとえると、戦士が少ないうちは、1人ずつ個別に管理してもよいかもしれません。

しかし、戦士が増え、サイヤ人系、炎の気を使う戦士、ナメック星の治療戦士、索敵役、支援役などが増えてくると、共通の土台や能力の約束で整理しないと、全体が見えにくくなります。

12章で学んだ抽象クラスとインターフェイスは、大きなプログラムを整理するための大切な設計道具です。

12章で学んだ内容を表で整理

学んだこと意味
抽象クラスを宣言できる未完成の共通土台を作れる
抽象クラスは抽象メソッドを持てる子クラスに必ず実装させたい処理を宣言できる
抽象クラスのオブジェクトは作れないそのままでは未完成だから
インターフェイスを宣言して実装できる能力や約束をクラスに持たせられる
インターフェイスのフィールドは定数になる固定された共通ルールを表す
インターフェイスのメソッドは抽象メソッドになる処理内容は実装クラスが決める
サブインターフェイスを宣言できる約束を段階的に拡張できる
多態性でまとめて扱える共通の型で呼び出し、実体ごとの処理を動かせる

図:12章で学んだクラス設計の全体像

以下の条件でイラストを作成してください。

カラーイラスト、16:9横長。白と青を基調にした学習教材風デザイン。ドラゴンボール風のアニメ調。背景は未来的な戦士チーム統合司令室。青いホログラム、クラス階層図、インターフェイスカード、配列カード、能力カードを配置する。

画面中央上部に大きく「12章のまとめ:土台・能力・多態性でクラスを整理する」と表示する。

画面上部中央に抽象クラスカードを配置する。

「abstract class Warrior」
「共通の土台」
「name / speed / showBaseInfo() / abstract show()」

画面左上にインターフェイスカードを配置する。

「interface iMovable」
「move();」
「移動できる約束」

画面右上にインターフェイスカードを配置する。

「interface iKiUser」
「useKi();」
「気を使える約束」

画面中央右にサブインターフェイスカードを配置する。

「interface iBattleFighter」
「extends iMovable」
「attack();」
「移動できて攻撃できる」

画面下部に3つの具体クラスカードを並べる。

左:
「SuperSaiyanWarrior」
「extends Warrior」
「implements iBattleFighter, iKiUser」

中央:
「FlameFighter」
「extends Warrior」
「implements iBattleFighter, iKiUser」

右:
「NamekianHealer」
「extends Warrior」
「implements iMovable」

各カードには小さなオリジナル戦士アイコンを入れる。
SuperSaiyanWarrior はワインレッドの道着、右胸に「亀」。
FlameFighter は赤と黒の原作に似ていない戦闘服、右胸に「界」。
NamekianHealer は白と青の修行服、右胸に「神」。
すべて原作のドラゴンボールのキャラクターに似ていない髪型と顔にする。特に目元を似せない。

画面下部に小さな配列カードを2つ置く。

「Warrior[]:戦士としてまとめる」
「iMovable[]:移動できる者としてまとめる」

画面最下部に説明文として「抽象クラスは共通の土台、インターフェイスは能力の約束、多態性は同じ命令で実体ごとの動きを実行する」と表示する。文字は白い半透明パネル上に配置する。

この図が示していること

この図では、12章で学んだ抽象クラス、インターフェイス、サブインターフェイス、多態性の関係をまとめています。

Warrior は、戦士としての共通の土台です。
iMovable や iKiUser は、能力の約束です。
iBattleFighter は、iMovable を拡張した発展的な能力の約束です。

具体クラスである SuperSaiyanWarrior、FlameFighter、NamekianHealer は、Warrior を継承し、必要なインターフェイスを実装します。

設計の要素役割
抽象クラス共通の土台を作る
インターフェイス能力の約束を作る
サブインターフェイス能力の約束を発展させる
具体クラス個別の戦士らしい処理を作る
多態性共通の型でまとめて扱う

この図から分かることは、12章で学んだ内容はバラバラの知識ではなく、大きなクラス設計を整理するためにつながっているということです。

12章でいちばん大事な感覚

12章で学んだことをひとつの感覚にまとめるなら、
共通の土台と共通の約束を使って、多くのクラスを整理しながら扱う考え方
です。

ドラゴンボール風に整理すると、次のようになります。

設計の考え方ドラゴンボール風のイメージ
抽象クラスで共通の土台を作る戦士としての名前や速度をまとめる
抽象メソッドで必須処理を決める戦士なら情報を表示できるようにする
インターフェイスで能力の約束を作る移動できる、気を使える、支援できる
サブインターフェイスで約束を発展させる移動できる者から、戦える者へ能力を広げる
具体クラスで個性を表すサイヤ人戦士、炎の戦士、治療戦士を作る
多態性でまとめて扱う同じ命令で、それぞれに合った動きをさせる

この感覚がつかめると、Javaのオブジェクト指向は、単なる継承の仕組みではなく、大きなプログラムを整理するための設計技法として見えてきます。

12章は、抽象クラスとインターフェイスを通して、Javaのクラス設計を大きく育てるための土台を作る章です。