Java超|共通の約束を作るインターフェイス

インターフェイスは、種族や流派を超えて「気を解放できる者」をひとつに束ねる共通奥義の契約です。

Javaのインターフェイスは、クラス同士を「何者か」ではなく、何ができるかでつなぐしくみです。

ドラゴンボールの世界観で考えると、サイヤ人、ナメック星人、地球人、界王流の戦士など、戦士の種類はさまざまです。

クラス何者か特徴
SuperSaiyanWarriorスーパーサイヤ人戦士変身して強大な気を放つ
NamekianHealerナメック星の治療戦士神秘的な気で仲間を回復する

この2つは、種族も役割も違います。
SuperSaiyanWarrior は攻撃型の戦士、NamekianHealer は回復型の戦士です。

しかし、どちらにも共通していることがあります。

それは、気を解放できることです。

この「気を解放できる」という共通能力を、Javaではインターフェイスとして表現できます。
この記事では、iWarrior インターフェイスを、気を解放する奥義 releaseKi() を必ず持つという共通の約束として扱います。

インターフェイスは「気を解放できる」という約束を作る

インターフェイスは、具体的な戦士そのものを作るものではありません。

たとえば、SuperSaiyanWarrior クラスはスーパーサイヤ人戦士という具体的な存在を表します。
NamekianHealer クラスはナメック星の治療戦士という具体的な存在を表します。

一方、iWarrior インターフェイスは、具体的な種族や職業を表すものではありません。

iWarrior は、次のような共通の約束を表します。

インターフェイスドラゴンボール風の意味
iWarrior気を扱える戦士の共通契約
releaseKi()気を解放する奥義
implements iWarriorこの戦士は気を解放できると宣言する

つまり、iWarrior は、
このクラスは releaseKi() という気を解放する奥義を必ず持つ
というルールを決めるものです。

ここで大切なのは、気の解放の仕方はクラスごとに違ってよいことです。

SuperSaiyanWarrior なら、金色のオーラをまとって戦闘力を高めます。
NamekianHealer なら、やさしい治療の気を放って仲間を回復します。

同じ releaseKi() という約束でも、中身はそれぞれのクラスらしく作れます。

図:インターフェイスは気を解放する共通契約

この図が示していること

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

SuperSaiyanWarrior と NamekianHealer は、クラスとして見ると別々の存在です。

クラス気の解放の仕方
SuperSaiyanWarrior金色の気を放ち、戦闘力を高める
NamekianHealer治療の気を放ち、仲間を回復する

しかし、どちらも iWarrior を実装しているため、releaseKi() を必ず持っています。

つまり、インターフェイスは「同じ種族かどうか」ではなく、同じ能力を持っているかどうかでクラスをつなぐしくみです。

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

Javaでは、インターフェイスを作るときに interface を使います。

interface iWarrior
{
    void releaseKi();
}

この iWarrior インターフェイスは、次の意味を持っています。

書いているもの意味
interface iWarrioriWarrior という共通契約を作る
void releaseKi();実装するクラスは気を解放する奥義を必ず持つ
メソッドの中身がない実際の気の解放方法は各クラスで決める

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

インターフェイスは、処理の完成品を書く場所ではありません。
このメソッドを必ず持つことという共通ルールを決める場所です。

ドラゴンボール風に考えると、iWarrior は「気を扱える戦士の証」です。

ただし、証そのものは戦いません。
実際に気を解放するのは、その証を受け入れた具体的なクラスです。

インターフェイスとクラスの違い

インターフェイスは、見た目だけを見るとクラスに少し似ています。
しかし、役割は大きく違います。

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

クラスは、具体的なオブジェクトを作るための設計図です。

たとえば、SuperSaiyanWarrior クラスは、スーパーサイヤ人戦士オブジェクトを作るための設計図です。
NamekianHealer クラスは、ナメック星の治療戦士オブジェクトを作るための設計図です。

一方、iWarrior インターフェイスは、具体的な戦士を作る設計図ではありません。
「気を解放できる」という共通能力の約束を表します。

Javaの要素ドラゴンボール風のイメージ
SuperSaiyanWarrior クラス変身して戦う具体的な戦士
NamekianHealer クラス治療の気を使う具体的な戦士
iWarrior インターフェイス気を解放できるという共通契約

つまり、クラスは「何者か」を表します。
インターフェイスは「何ができるか」を表します。

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

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

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

フィールドは public static final として扱われるため、あとから自由に変更する普通の変数というより、定数として扱われます。

メソッドは public abstract として扱われます。
つまり、基本的には処理内容を書かず、メソッド名と形だけを決めます。

interface iWarrior
{
    void releaseKi();
}

この releaseKi() は、次のような約束です。

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

ドラゴンボール風に言うと、インターフェイスに書くのは「共通奥義名」です。

iWarrior には、
気を解放する奥義を持つこと
とだけ書かれています。

その気をどう解放するかは、各クラスで決めます。

インターフェイスそのものからオブジェクトは作れない

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

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

interface iWarrior
{
    void releaseKi();
}

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

iWarrior warrior = new iWarrior();

これはできません。

なぜなら、iWarrior は「気を解放できる」という約束であって、具体的な戦士ではないからです。

ドラゴンボール風にたとえるなら、iWarrior は「気を扱える者の証」です。
証そのものが気を放つわけではありません。

実際に気を放つのは、SuperSaiyanWarrior や NamekianHealer のように、iWarrior を実装した具体的なクラスです。

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

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

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

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

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

class SuperSaiyanWarrior implements iWarrior
{
}

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

ドラゴンボール風に言えば、SuperSaiyanWarrior が、
自分は気を解放する奥義 releaseKi() を使えます
と宣言しているようなものです。

そのため、SuperSaiyanWarrior クラスは、iWarrior に書かれている releaseKi() を必ず定義する必要があります。

public void releaseKi()
{
    System.out.println("スーパーサイヤ人戦士が金色の気を解放します。");
}

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

実装クラスは約束されたメソッドを定義する

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

void releaseKi();

iWarrior を実装するクラスでは、この releaseKi() の処理内容を作る必要があります。

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

たとえば、SuperSaiyanWarrior の releaseKi() では、金色のオーラを解放します。

NamekianHealer の releaseKi() では、治療の気を解放します。

同じ releaseKi() という名前でも、実際の動きはクラスごとに違ってよいです。

ファイル名:Sample3.java

interface iWarrior
{
    void releaseKi();
}

class SuperSaiyanWarrior implements iWarrior
{
    private String name;
    private String transformationStage;

    public SuperSaiyanWarrior(String n, String ts)
    {
        name = n;
        transformationStage = ts;
        System.out.println(name + " 変身段階「" + transformationStage + "」のスーパーサイヤ人戦士を作成しました。");
    }

    public void releaseKi()
    {
        System.out.println(name + "が" + transformationStage + "の姿で金色の気を解放しました。");
        System.out.println("周囲に激しい戦闘オーラが広がります。");
    }
}

class NamekianHealer implements iWarrior
{
    private String name;
    private String healingTechnique;

    public NamekianHealer(String n, String ht)
    {
        name = n;
        healingTechnique = ht;
        System.out.println(name + " 治療術「" + healingTechnique + "」を使うナメック星の治療戦士を作成しました。");
    }

    public void releaseKi()
    {
        System.out.println(name + "が治療術「" + healingTechnique + "」の気を解放しました。");
        System.out.println("やさしい光の気が仲間を包み込みます。");
    }
}

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

        warriors[0] = new SuperSaiyanWarrior("蒼月", "蒼き覚醒");
        warriors[1] = new NamekianHealer("ネイロン", "光の再生術");

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

実行結果

蒼月 変身段階「蒼き覚醒」のスーパーサイヤ人戦士を作成しました。
ネイロン 治療術「光の再生術」を使うナメック星の治療戦士を作成しました。
蒼月が蒼き覚醒の姿で金色の気を解放しました。
周囲に激しい戦闘オーラが広がります。
ネイロンが治療術「光の再生術」の気を解放しました。
やさしい光の気が仲間を包み込みます。

iWarrior は releaseKi() という気の奥義だけを約束する

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

interface iWarrior
{
    void releaseKi();
}

ここに書かれているのは releaseKi() だけです。

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

ただし、releaseKi() の中身はここでは書かれていません。

実際の処理は、SuperSaiyanWarrior と NamekianHealer が、それぞれ自分の内容に合わせて定義します。

要素決めていること
iWarriorreleaseKi() を持つこと
SuperSaiyanWarrior金色の戦闘オーラを解放する
NamekianHealer治療の気を解放する

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

SuperSaiyanWarrior と NamekianHealer は implements で約束を引き受けている

SuperSaiyanWarrior と NamekianHealer は、どちらも iWarrior を実装しています。

class SuperSaiyanWarrior implements iWarrior

class NamekianHealer implements iWarrior

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

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

クラスreleaseKi() の内容
SuperSaiyanWarrior変身段階に応じた金色の気を解放する
NamekianHealer治療術としてやさしい気を解放する

SuperSaiyanWarrior の releaseKi() は次のようになっています。

public void releaseKi()
{
    System.out.println(name + "が" + transformationStage + "の姿で金色の気を解放しました。");
    System.out.println("周囲に激しい戦闘オーラが広がります。");
}

NamekianHealer の releaseKi() は次のようになっています。

public void releaseKi()
{
    System.out.println(name + "が治療術「" + healingTechnique + "」の気を解放しました。");
    System.out.println("やさしい光の気が仲間を包み込みます。");
}

同じ releaseKi() という名前でも、実行される内容は違います。

SuperSaiyanWarrior は攻撃的な気を放ちます。
NamekianHealer は回復のための気を放ちます。

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

図:implements で気を解放する約束を引き受ける

この図が示していること

この図では、iWarrior インターフェイスが releaseKi() という約束を作り、SuperSaiyanWarrior と NamekianHealer が implements によってその約束を引き受けている様子を表しています。

ポイントは、releaseKi() というメソッド名は共通でも、実際の気の使い方はクラスごとに違ってよいことです。

クラスreleaseKi() の動き
SuperSaiyanWarrior金色の戦闘オーラを解放する
NamekianHealer治療の気を解放する

つまり、インターフェイスは「共通の奥義名」を作り、実際の動きは各クラスが自分らしく実装します。

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

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

iWarrior[] warriors;
warriors = new iWarrior[2];

iWarrior はインターフェイスなので、new iWarrior() のように直接オブジェクトを作ることはできません。

しかし、iWarrior 型の配列は作れます。
そして、その配列には iWarrior を実装しているクラスのオブジェクトを入れることができます。

warriors[0] = new SuperSaiyanWarrior("蒼月", "蒼き覚醒");
warriors[1] = new NamekianHealer("ネイロン", "光の再生術");

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

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

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

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

呼び出し側は、SuperSaiyanWarrior なのか NamekianHealer なのかを細かく気にしていません。

なぜなら、iWarrior を実装しているなら releaseKi() を持っていると分かっているからです。

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

実体実行される releaseKi()
SuperSaiyanWarriorSuperSaiyanWarrior の releaseKi()
NamekianHealerNamekianHealer の releaseKi()

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

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

インターフェイスで「気を解放できる」を型にできる

オブジェクト指向では、クラスを「何者か」で整理することがあります。

クラス何者か
SuperSaiyanWarrior変身して戦うスーパーサイヤ人戦士
NamekianHealer治療術を使うナメック星の戦士

しかし、インターフェイスを使うと、「何ができるか」で整理できます。

クラス何ができるか
SuperSaiyanWarriorreleaseKi() で気を解放できる
NamekianHealerreleaseKi() で気を解放できる

SuperSaiyanWarrior と NamekianHealer は、クラスとしては別の存在です。
しかし、どちらも releaseKi() で気を解放できます。

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

ドラゴンボール風にたとえるなら、サイヤ人かナメック星人かは違っていても、同じく「気を扱える者」としてまとめられるということです。

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

インターフェイスは、抽象クラスと少し似ています。
どちらも、複数のクラスをまとめて扱うために役立ちます。

また、どちらも未完成な要素を持てるため、そのまま直接オブジェクトを作ることはできません。

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

比較項目抽象クラスインターフェイス
主な役割共通の土台共通の約束
通常フィールド持てる原則として持たない
変更可能な状態持てる原則として持たない
通常メソッド持てる原則として処理を書かない
コンストラクタ持てる持てない
使うキーワードextendsimplements

ドラゴンボール風にたとえると、抽象クラスは「戦士としての共通の土台」です。

たとえば、名前、戦闘力、修行する処理などを持たせたい場合に向いています。

一方、インターフェイスは「戦士が守る能力の約束」です。

たとえば、releaseKi() を持つこと、fly() を持つこと、heal() を持つことのような約束を表します。

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

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

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

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

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

iWarrior を見て分かること内容
releaseKi() を持つ気を解放できる
実装クラスが中身を作る気の使い方はクラスごとに違ってよい
iWarrior 型で扱える共通の型としてまとめられる

つまり、iWarrior を実装しているクラスは、必ず releaseKi() を使えます。

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

warriors[i].releaseKi();

この1行だけで、SuperSaiyanWarrior なら金色の気を解放し、NamekianHealer なら治療の気を解放します。

これは、オブジェクト指向らしい自然な設計です。

呼び出す側は「releaseKi() が使える」という約束だけを信じればよく、具体的な中身は各クラスに任せられます。

図:インターフェイス型配列で気を扱える戦士をまとめる

この図が示していること

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

iWarrior はインターフェイスなので、iWarrior 自身の実体は作れません。

しかし、iWarrior を実装したクラスのオブジェクトを、iWarrior 型の配列に入れることはできます。

配列要素実体
warriors[0]SuperSaiyanWarrior
warriors[1]NamekianHealer

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

warriors[i].releaseKi();

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

実体動くメソッド
SuperSaiyanWarriorSuperSaiyanWarrior の releaseKi()
NamekianHealerNamekianHealer の releaseKi()

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

インターフェイスの考え方をドラゴンボール風に整理する

インターフェイスは、ドラゴンボール風にたとえると、戦士が持つ能力や共通ルールの約束です。

今回の iWarrior は、気を解放できることを表しています。

クラスreleaseKi() の中身
SuperSaiyanWarrior金色の戦闘オーラを解放する
NamekianHealer治療の気を解放する

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

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

SuperSaiyanWarrior と NamekianHealer は、種族も役割も違います。

しかし、どちらも iWarrior を実装していれば、releaseKi() を持つものとして同じ型で扱えます。

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