Java道|12章のまとめ

抽象クラスで共通の土台を整え、インターフェイスで能力の約束を結ぶ。
12章は、Javaのオブジェクト指向を「クラスを作る学習」から「大きな設計を組み立てる学習」へ進める章です。

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

11章で継承の基本を学ぶと、親クラスから子クラスへ機能を受け継ぐ考え方が見えてきます。
しかし、実際のプログラム設計では、それだけでは足りない場面があります。

たとえば、鬼滅の刃風にたとえると、和風剣士たちはそれぞれ違う個性を持っています。

和風剣士の種類個性
WaterSlayer水の呼吸を使う剣士
FlameSlayer炎の呼吸を使う剣士
SupportSlayer仲間を支援する剣士
ScoutSlayer索敵を得意とする剣士

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

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

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

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

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

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

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

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

抽象クラスは完成品ではなく、サブクラスのための共通の土台です。

鬼滅の刃風にたとえると、抽象クラス Slayer は、「和風剣士なら最低限こういうものを持つ」という設計図です。

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

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

WaterSlayer なら、水の呼吸の型を表示したいかもしれません。
FlameSlayer なら、炎の技を表示したいかもしれません。
SupportSlayer なら、治療や補助の能力を表示したいかもしれません。

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

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

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

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

public abstract void show();

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

鬼滅の刃風にたとえると、隊の規則として「剣士は任務前に自分の情報を示せ」と決めているようなものです。

ただし、水の剣士と炎の剣士と支援剣士では、名乗る内容が違います。

クラスshow() で表示する内容
WaterSlayer名前、速度、水の呼吸
FlameSlayer名前、速度、炎の技
SupportSlayer名前、速度、支援技術

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

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

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

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

// これはできない
Slayer slayer = new Slayer();

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

鬼滅の刃風にたとえると、Slayer は「剣士」という共通概念です。
しかし、「剣士」という概念だけでは、実際に任務へ出る人物にはなりません。

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

抽象的な土台具体的なクラス
SlayerWaterSlayer
SlayerFlameSlayer
SlayerSupportSlayer

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

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

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

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

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

鬼滅の刃風に考えると、和風剣士にはいろいろな能力があります。

能力インターフェイスの例意味
移動できるiMovablemove() を持つ
呼吸を使えるiBreathUseruseBreath() を持つ
情報を表示できるiDisplayableshow() を持つ
支援できるiSupportablesupport() を持つ

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

WaterSlayer は水の剣士です。
SupportSlayer は支援剣士です。

この2つは、クラスとしては違う存在です。
しかし、どちらも移動できるなら、iMovable を実装できます。
どちらも情報を表示できるなら、iDisplayable を実装できます。

クラス何者か何ができるか
WaterSlayer水の剣士移動できる、呼吸を使える、表示できる
FlameSlayer炎の剣士移動できる、呼吸を使える、表示できる
SupportSlayer支援剣士移動できる、支援できる、表示できる

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

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

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

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

鬼滅の刃風にたとえると、インターフェイスに書くフィールドは、個々の剣士の現在の体力や速度ではありません。
それよりも、隊全体で共有する固定ルールのようなものです。

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

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

interface iMissionRule
{
    int MAX_MISSION_LEVEL = 10;
}

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

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

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

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

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

interface iMovable
{
    void move();
}

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

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

クラスmove() の中身
WaterSlayer水の流れのように移動する
FlameSlayer炎の勢いで前線へ踏み込む
SupportSlayer仲間の近くへ素早く移動する

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

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

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

たとえば、WaterSlayer、FlameSlayer、SupportSlayer がすべて iMovable を実装しているとします。

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

iMovable[] members = new iMovable[3];

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

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

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

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

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

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

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

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

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

interface iBattleSlayer extends iMovable
{
    void attack();
}

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

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

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

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

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

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

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

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

鬼滅の刃風に整理すると、次のようになります。

役割しくみ
剣士としての共通の土台抽象クラスSlayer
移動できる能力の約束インターフェイスiMovable
呼吸を使える能力の約束インターフェイスiBreathUser
実際の剣士具体クラスWaterSlayer、FlameSlayer、SupportSlayer

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

Slayer 型なら、剣士としてまとめられます。
iMovable 型なら、移動できる者としてまとめられます。
iBreathUser 型なら、呼吸を使える者としてまとめられます。

まとめ方見方
剣士としてまとめるSlayer[]共通の土台で見る
移動できる者としてまとめるiMovable[]能力で見る
呼吸を使える者としてまとめるiBreathUser[]能力で見る

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

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

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

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

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

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

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

鬼滅の刃風にたとえると、隊士が少ないうちは、1人ずつ個別に管理してもよいかもしれません。
しかし、隊士が増え、柱、支援剣士、索敵役、治療役などが増えてくると、共通の土台や能力の約束で整理しないと、全体が見えにくくなります。

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

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

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

図:共通の型でまとめて扱う多態性

↓クリックすると拡大表示されます。

この図が示していること

この図では、12章で学んだ多態性の使い方を整理しています。

左側の Slayer[] slayers では、WaterSlayer、FlameSlayer、SupportSlayer を剣士としてまとめています。
この場合、共通して show() を呼び出せます。

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

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

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

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

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

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

鬼滅の刃風に整理すると、次のようになります。

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

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

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