Java道|インターフェイスで共通ルールを作る

「何者か」ではなく、「何ができるか」でクラスをつなぐ。
インターフェイスを理解すると、共通ルールを守るクラスを、すっきり自然にまとめられるようになります。

12章では、抽象クラスに続いて、もうひとつ大切なしくみである インターフェイス を学びます。

インターフェイスは、見た目はクラスに少し似ています。
しかし、役割はかなりはっきりしています。

インターフェイスは、具体的な状態をたくさん持つためのものではありません。
主な役割は、
この型に属するものは、このメソッドを必ず持っている
という共通ルールを決めることです。

鬼滅の刃風にたとえると、インターフェイスは「剣士なら show() で情報を示せること」「飛行できる者なら fly() を持つこと」のような、能力や役割の約束に近いです。

クラスや抽象クラスは、WaterSlayer や MedicalSlayer のように「何者か」を表しやすいです。
一方、インターフェイスは、iSlayer のように「何ができるか」「どんな約束を守るか」を表します。

種類表しやすいもの鬼滅の刃風のイメージ
クラス具体的な存在水の剣士、支援剣士
抽象クラス共通の土台剣士としての基本設計
インターフェイス能力や約束情報を表示できる、任務を遂行できる

Javaでは、クラスがインターフェイスを実装することで、複数のクラスに同じメソッド名の約束を持たせられます。

その結果、見た目や中身が違うクラスでも、同じインターフェイスを実装していれば、同じ型としてまとめて扱えるようになります。

インターフェイスとは何か

インターフェイスは、class の代わりに interface を使って宣言する特別な型です。

interface iSlayer
{
    void show();
}

この iSlayer インターフェイスは、
iSlayer を実装するクラスは、必ず show() を持ちなさい
という約束を表しています。

ここで大切なのは、show() の中身はまだ決まっていないことです。

つまり、インターフェイスは処理の中身を作る場所というより、
メソッド名と役割のルールを決める場所
です。

鬼滅の刃風にたとえると、iSlayer は「この印を持つ剣士は、自分の情報を示す show() を必ず使える」という隊の共通規則です。

WaterSlayer なら、水の呼吸や名前を表示する show() を作ります。
MedicalSlayer なら、治療技術や名前を表示する show() を作ります。

同じ show() という約束を守りながら、中身はクラスごとに違ってよいのです。

インターフェイスはクラスと何が違うのか

インターフェイスは、見た目だけを見るとクラスに似ています。
しかし、性質はかなり違います。

比較項目クラスインターフェイス
宣言キーワードclassinterface
オブジェクト作成できるできない
コンストラクタ持てる持てない
フィールド通常のフィールドを持てる原則として定数になる
メソッド処理内容を書ける原則として宣言だけを書く
主な役割具体的な存在を作る共通の約束を作る

クラスは、具体的なオブジェクトを作るための設計図です。
一方、インターフェイスは、実体そのものを作るというより、クラスが守るべき約束を示します。

鬼滅の刃風にたとえると、WaterSlayer クラスは「水の剣士」という具体的な存在です。
MedicalSlayer クラスは「支援剣士」という具体的な存在です。

それに対して iSlayer インターフェイスは、
「剣士として情報を表示できること」
という能力の約束です。

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

インターフェイスのフィールドとメソッドの特徴

インターフェイスでは、メンバに何も修飾子を書かなくても、特別な意味になります。

フィールドは public static final として扱われます。
メソッドは public abstract として扱われます。

インターフェイスのメンバ実際の意味
フィールドpublic static final
メソッドpublic abstract

そのため、インターフェイスのフィールドは、普通のクラスのようにあとから自由に値を変えるためのものではありません。
基本的には定数として扱われます。

また、メソッドは原則として処理内容を書かず、宣言だけを置きます。

interface iSlayer
{
    void show();
}

この show() は、次のような意味になります。

書いた形意味
void show();実装クラスは show() を必ず定義する
中身がない実際の処理は各クラスに任せる

鬼滅の刃風に言うと、インターフェイスに書くのは「隊の共通任務名」です。
実際にその任務をどう行うかは、水の剣士や支援剣士など、各クラス側で決めます。

インターフェイスはオブジェクトを作成できない

インターフェイスそのものは、new を使ってオブジェクトを作成できません。

たとえば、次のようなインターフェイスがあるとします。

interface iSlayer
{
    void show();
}

この場合、次のようには書けません。

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

インターフェイスは、具体的な処理を持つ完成品ではないからです。

鬼滅の刃風にたとえると、iSlayer は「情報を示せる剣士である」という約束です。
しかし、約束そのものは具体的な人物ではありません。

実際に任務へ出られるのは、WaterSlayer や MedicalSlayer のように、その約束を実装した具体的なクラスです。

ただし、インターフェイスは変数や配列の型として使えます。

できることできないこと
iSlayer 型の変数を作るnew iSlayer() はできない
iSlayer 型の配列を作るインターフェイス自身の実体は作れない
実装クラスのオブジェクトを代入するshow() の中身なしでは動けない

この性質が、インターフェイスの大きな特徴です。

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

インターフェイスを実装するとはどういうことか

インターフェイスは、クラスと組み合わせて使います。

クラスがインターフェイスの約束を引き受けることを、
インターフェイスを実装する
といいます。

このとき使うのが implements です。

class WaterSlayer implements iSlayer
{
}

このように書くと、WaterSlayer クラスは iSlayer インターフェイスを実装していることになります。

鬼滅の刃風にたとえると、WaterSlayer が、
「私は iSlayer の約束を守ります」
と宣言しているようなものです。

そのため、WaterSlayer クラスは iSlayer に書かれている show() を、きちんと定義しなければなりません。

public void show()
{
    System.out.println("水の剣士の情報を表示します。");
}

つまり、implements は「このクラスは、このインターフェイスの約束を実行できます」と示すための書き方です。

実装したクラスはインターフェイスのメソッドを定義する必要がある

インターフェイスには、メソッドの名前と形だけが書かれています。

たとえば、iSlayer に次のメソッドがあるとします。

void show();

iSlayer を実装するクラスでは、この show() の処理内容を定義する必要があります。

インターフェイス実装クラスの責任
show() を持つことを約束するshow() の中身を書く
処理内容は書かないクラスに合った処理を書く
共通メソッド名を決める実際の動きを作る

鬼滅の刃風にたとえると、iSlayer は「この隊士は情報を示せること」とだけ決めています。
でも、水の剣士が何を表示するか、支援剣士が何を表示するかは、それぞれのクラスで決めます。

このように、インターフェイスは約束を作り、実装クラスがその約束を具体化します。

インターフェイスを実装する

ファイル名:Sample3.java

interface iSlayer
{
    void show();
}

class WaterSlayer implements iSlayer
{
    private String name;
    private String breathingStyle;

    public WaterSlayer(String n, String bs)
    {
        name = n;
        breathingStyle = bs;
        System.out.println(name + " 水の呼吸「" + breathingStyle + "」の剣士を作成しました。");
    }

    public void show()
    {
        System.out.println("水の剣士の名前は" + name + "です。");
        System.out.println("呼吸の型は" + breathingStyle + "です。");
    }
}

class MedicalSlayer implements iSlayer
{
    private String name;
    private String healingSkill;

    public MedicalSlayer(String n, String hs)
    {
        name = n;
        healingSkill = hs;
        System.out.println(name + " 治療技術「" + healingSkill + "」の支援剣士を作成しました。");
    }

    public void show()
    {
        System.out.println("支援剣士の名前は" + name + "です。");
        System.out.println("治療技術は" + healingSkill + "です。");
    }
}

class Sample3
{
    public static void main(String[] args)
    {
        iSlayer[] slayers;
        slayers = new iSlayer[2];

        slayers[0] = new WaterSlayer("水月", "水流斬り");
        slayers[1] = new MedicalSlayer("胡蝶香", "藤花治療");

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

実行結果

水月 水の呼吸「水流斬り」の剣士を作成しました。
胡蝶香 治療技術「藤花治療」の支援剣士を作成しました。
水の剣士の名前は水月です。
呼吸の型は水流斬りです。
支援剣士の名前は胡蝶香です。
治療技術は藤花治療です。

iSlayer は show() という約束だけを持つ

このプログラムでは、まず iSlayer というインターフェイスを作っています。

interface iSlayer
{
    void show();
}

ここには show() しか書かれていません。

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

ただし、show() の中身はありません。
WaterSlayer と MedicalSlayer が、それぞれ自分の内容に合わせて show() を定義します。

インターフェイス決めていること
iSlayershow() を持つこと
WaterSlayer水の剣士としての show() を作る
MedicalSlayer支援剣士としての show() を作る

つまり、インターフェイスは約束だけを決め、実際の処理はクラスに任せています。

WaterSlayer と MedicalSlayer は implements で約束を引き受けている

WaterSlayer と MedicalSlayer は、どちらも iSlayer を実装しています。

class WaterSlayer implements iSlayer

class MedicalSlayer implements iSlayer

implements iSlayer と書くことで、
このクラスは iSlayer の約束を守ります
と宣言しています。

そのため、どちらのクラスにも show() が定義されています。

クラスshow() の内容
WaterSlayer名前、呼吸の型を表示する
MedicalSlayer名前、治療技術を表示する

同じ show() という名前でも、表示する内容は違います。

WaterSlayer の show() では、水の剣士らしい情報を表示しています。

public void show()
{
    System.out.println("水の剣士の名前は" + name + "です。");
    System.out.println("呼吸の型は" + breathingStyle + "です。");
}

MedicalSlayer の show() では、支援剣士らしい情報を表示しています。

public void show()
{
    System.out.println("支援剣士の名前は" + name + "です。");
    System.out.println("治療技術は" + healingSkill + "です。");
}

インターフェイスは、show() という共通の入口だけを決めています。
中身は、それぞれのクラスらしく作れます。

インターフェイス型の配列でまとめて扱える

このプログラムの大きなポイントは、iSlayer[] 型の配列を作っているところです。

iSlayer[] slayers;
slayers = new iSlayer[2];

iSlayer はインターフェイスなので、new iSlayer() のように直接オブジェクトは作れません。

しかし、iSlayer 型の配列は作れます。
そして、その配列には iSlayer を実装しているクラスのオブジェクトを入れられます。

slayers[0] = new WaterSlayer("水月", "水流斬り");
slayers[1] = new MedicalSlayer("胡蝶香", "藤花治療");

整理すると、次のようになります。

配列要素変数としての型実際に入っているオブジェクト
slayers[0]iSlayerWaterSlayer
slayers[1]iSlayerMedicalSlayer

そして、ループでは同じように show() を呼び出しています。

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

呼び出し側は、WaterSlayer なのか MedicalSlayer なのかを細かく気にしていません。
iSlayer を実装しているなら show() を持っている、と分かっているからです。

実際に動く show() は、オブジェクトの実体によって変わります。

実体実行される show()
WaterSlayerWaterSlayer の show()
MedicalSlayerMedicalSlayer の show()

ここにインターフェイスの強さがあります。

クラスの中身が違っていても、共通の約束を持っているなら、同じ型としてまとめて扱えます。

抽象クラスとの共通点と違い

インターフェイスは、抽象クラスと少し似ています。

どちらも、複数のクラスをまとめて扱うために役立ちます。
また、どちらも未完成な要素を持てるため、そのまま直接オブジェクトを作ることはできません。

ただし、役割には違いがあります。

比較項目抽象クラスインターフェイス
主な役割共通の土台共通の約束
通常フィールド持てる原則として持てない
変更可能な状態持てる原則として持たない
通常メソッド持てる原則として処理を書かない
コンストラクタ持てる持てない
実装の考え方extends で継承するimplements で実装する

鬼滅の刃風にたとえると、抽象クラスは「剣士としての共通の土台」です。
たとえば、speed のような共通状態や setSpeed() のような共通処理を持てます。

一方、インターフェイスは「剣士が守る能力の約束」です。
たとえば、show() を持つこと、fly() を持つこと、support() を持つことのような約束を表します。

使い分け向いているもの
共通の状態や処理も持たせたい抽象クラス
共通のメソッド名や能力だけを約束したいインターフェイス

この違いを押さえると、抽象クラスとインターフェイスを混同しにくくなります。

なぜインターフェイスを使うと分かりやすいコードになるのか

インターフェイスを使うと、何を共通ルールとして扱いたいのかがはっきりします。

今回なら、iSlayer を見ただけで、次のことが分かります。

iSlayer を見て分かること内容
show() を持つ情報を表示できる
実装クラスが中身を作る表示内容はクラスごとに違ってよい
iSlayer 型で扱える共通の型としてまとめられる

つまり、iSlayer を実装しているクラスは、必ず show() を使って情報を表示できます。

呼び出す側は、細かいクラス名を意識しなくても、次のように書けます。

slayers[i].show();

鬼滅の刃風にたとえると、隊士の見た目や能力は違っていても、iSlayer を実装しているなら、全員が show() で情報を示せるということです。

この共通ルールがあるから、コードがすっきりします。

オブジェクト指向らしい自然な設計につながる

オブジェクト指向では、クラスを「何者か」で整理することもあります。
一方で、「何ができるか」で整理したい場面もあります。

たとえば、WaterSlayer と MedicalSlayer は、まったく違うクラスです。

クラス何者か
WaterSlayer水の剣士
MedicalSlayer支援剣士

しかし、どちらも情報を表示できる、という共通点があります。

クラス何ができるか
WaterSlayershow() で情報を表示できる
MedicalSlayershow() で情報を表示できる

この「何ができるか」を型として表現できるのがインターフェイスです。

つまり、インターフェイスは、
何者かではなく、何ができるかでクラスをまとめるためのしくみ
です。

この考え方が分かると、クラス同士の関係をより柔軟に設計できるようになります。

図:インターフェイスは共通の約束を作る

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

この図が示していること

この図では、iSlayer インターフェイスが show() という共通の約束を持っていることを表しています。

上部の iSlayer は、show() の中身までは持っていません。
ただし、iSlayer を実装するクラスは show() を必ず持つ、というルールを決めています。

下部の WaterSlayer と MedicalSlayer は、それぞれ implements iSlayer によって、その約束を引き受けています。
そして、それぞれのクラスに合った show() を実装しています。

クラスshow() の中身
WaterSlayer名前・呼吸の型を表示
MedicalSlayer名前・治療技術を表示

この図から分かることは、インターフェイスは処理そのものではなく、共通のメソッド名と役割を約束する仕組みだということです。

図:インターフェイス型の配列でまとめて扱う

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

この図が示していること

この図では、iSlayer[] 配列に WaterSlayer と MedicalSlayer のオブジェクトを入れて、同じ show() でまとめて扱う流れを表しています。

iSlayer はインターフェイスなので、new iSlayer() のように直接オブジェクトを作ることはできません。
しかし、iSlayer 型の配列や変数として使うことはできます。

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

配列要素実体
slayers[0]WaterSlayer
slayers[1]MedicalSlayer

ループでは、どちらにも同じように show() を呼び出しています。

slayers[i].show();

それでも、実際に動く show() は実体によって変わります。

実体動くメソッド
WaterSlayerWaterSlayer の show()
MedicalSlayerMedicalSlayer の show()

この図から分かることは、インターフェイスを使うと、違うクラスでも共通の約束をもとに同じ型でまとめて扱えるということです。

鬼滅の刃風にインターフェイスを整理する

インターフェイスは、鬼滅の刃風にたとえると、
剣士が持つ能力や共通ルールの約束
です。

iSlayer を実装しているなら、必ず show() を持ちます。
ただし、show() の中身はクラスごとに違ってよいです。

クラスshow() の中身
WaterSlayer水の剣士として名前と呼吸の型を表示
MedicalSlayer支援剣士として名前と治療技術を表示

つまり、インターフェイスは次のような考え方です。

考え方内容
インターフェイス共通の約束を決める
implementsクラスが約束を引き受ける
実装クラス約束されたメソッドの中身を作る
インターフェイス型実装クラスをまとめて扱える

インターフェイスを使うと、クラスの種類が違っていても、同じ能力や約束を持つものとして扱えます。

この内容で押さえておきたいポイント

ポイント内容
インターフェイス共通の約束を定める特別な型
宣言interface を使う
実装implements を使う
オブジェクト作成インターフェイス自身からは直接作れない
フィールド原則として public static final
メソッド原則として public abstract
実装クラスの責任インターフェイスのメソッドを定義する
インターフェイス型配列実装クラスのオブジェクトをまとめて扱える
抽象クラスとの違い抽象クラスは共通の土台、インターフェイスは共通の約束

インターフェイスは、何者かを表すためのものではなく、何ができるかを表すためのしくみです。

WaterSlayer と MedicalSlayer は別々のクラスです。
しかし、どちらも iSlayer を実装していれば、show() を持つものとして同じ型で扱えます。

この感覚がつかめると、Javaのオブジェクト指向で「共通ルールを作る」意味がかなり見えやすくなります。