Java入門|インターフェイスのしくみと実装方法

「何者か」ではなく、「何ができるか」でクラスをつなぐ。
インターフェイスを理解すると、戦士たちの共通ルールを、もっとすっきり自然に表現できるようになる。

12章では、抽象クラスに続いて、もうひとつ大切なしくみであるインターフェイスを学びます。
インターフェイスは、見た目はクラスに少し似ていますが、役割はかなりはっきりしています。クラスのように具体的な状態をたくさん持つためのものではなく、この型に属するものは、こういうメソッドを必ず持っている という約束を表すためのものです。

ドラゴンボールでたとえると、インターフェイスは「戦士なら show を必ず持つ」「飛べる戦士なら fly を必ず持つ」といった、能力や共通ルールの宣言に近いです。
サイヤ人か、ナメック星人戦士か、といった「何者か」を表すのがクラスや抽象クラスだとすれば、インターフェイスは「何ができるか」「どんな約束を守るか」を表すものだと考えると、とてもわかりやすくなります。

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

  • クラスどうしの見た目が違っていても
  • 同じインターフェイスを実装していれば
  • 同じ型としてまとめて扱える

ようになります。

今回は、「インターフェイスのしくみと実装方法」を、ドラゴンボールの世界観で置きかえながら、やわらかく丁寧に整理していきます。特に、

  • インターフェイスとは何か
  • クラスとの違いはどこにあるのか
  • なぜオブジェクトを作れないのか
  • implements で何をしているのか
  • インターフェイス型でまとめて扱えると、なぜ便利なのか

を順番に見ていきます。

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

インターフェイスは、class の代わりに interface を使って宣言する特別な型です。
そこにはフィールドとメソッドを書けますが、ふつうのクラスとはかなり性質が違います。

いちばん大事なのは、インターフェイスが
共通の約束を定めるためのもの
だということです。

たとえば、ドラゴンボール風に

interface iWarrior
{
    void show();
}

のようなインターフェイスがあれば、これは
「iWarrior を実装するクラスは、必ず show を持ちなさい」
という約束になります。

ここで大事なのは、まだ show の中身は決まっていないことです。
つまりインターフェイスは、処理の中身を作る場所というより、メソッド名と役割のルールを決める場所 と考えるとわかりやすいです。

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

見た目はクラスに似ていますが、インターフェイスにはいくつか大きな違いがあります。

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

この表から見えてくるのは、インターフェイスは「完成した実体の設計図」というより、共通のルールだけを抜き出したもの だということです。

ドラゴンボールでたとえるなら、クラスは「サイヤ人」や「ナメック星人戦士」のような具体的な存在に近いです。
それに対してインターフェイスは、「表示機能を持つ」「この技を使える」といった能力の約束に近いです。

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

インターフェイスでは、通常、メンバに何も修飾子を書きません。
でも、何も書かなくても、フィールドには public static final、メソッドには abstract が付いているのと同じ意味になります。

つまり、

  • フィールドは定数
  • メソッドは抽象メソッド

として扱われます。

この点を表で整理すると、こうなります。

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

だからインターフェイスでは、ふつうのクラスのように「あとから値が変わるフィールド」を持たせるのには向きません。
また、メソッドの中身も原則として書きません。

ドラゴンボール風に言えば、インターフェイスに書くのは「絶対に守るべき戦士のルール名」だけであって、そのルールの実際の動きは、各クラス側に任せるということです。

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

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

これは、抽象クラスと少し似ています。
なぜなら、インターフェイスも具体的な処理を持っておらず、未完成だからです。

ドラゴンボールでたとえると、「show を持つべき」という約束そのものはあっても、それだけではまだ具体的な戦士になっていません。

  • サイヤ人としての show
  • 飛行機に対応する別の型の show

のように、実際の中身が決まってはじめて、オブジェクトとして使えるようになります。

つまりインターフェイスは、

  • 変数や配列の型にはなれる
  • でもそれ自体の実体は作れない

という立場です。

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

インターフェイスはクラスと組み合わせて使います。
クラスがインターフェイスのルールを引き受けることを、インターフェイスを実装する と言います。
このとき使うのが implements です。

たとえば、

class Saiyan implements iWarrior
{
    ...
}

と書けば、Saiyan クラスは iWarrior インターフェイスを実装していることになります。

これは、ドラゴンボール風に言えば
「この戦士は、iWarrior という約束を守ります」
と宣言しているのと同じです。

そのため、このクラスは iWarrior に書かれているメソッドを、きちんと定義しなければなりません。

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

インターフェイスには、メソッドの名前と形だけが書かれています。
そのため、インターフェイスを実装したクラスでは、そのメソッドの処理内容をきちんと定義しなければなりません。

たとえば iWarrior に

void show();

があるなら、Saiyan も NamekianWarrior も、自分のクラスの中に show() を定義する必要があります。

つまりインターフェイスは、
このメソッド名を必ず持ちなさい
という約束をしているわけです。

ドラゴンボールで考えるなら、
「iWarrior を名乗るなら、show という技を必ず持つ」
というルールです。
ただし、その技の中身はサイヤ人とナメック星人戦士で違っていてかまいません。

サンプルプログラム:インターフェイスの実装

ここでは、ドラゴンボール風のクラスで、インターフェイスの実装を見ていきます。
iWarrior、Saiyan、NamekianWarrior を用いて整理します。内容は、インターフェイス iWarrior に show() だけを宣言し、それを 2 つのクラスが実装して、インターフェイス型の配列でまとめて扱う流れです。

ファイル名:Sample3.java

interface iWarrior
{
    void show();
}

class Saiyan implements iWarrior
{
    private String name;
    private int power;

    public Saiyan(String n, int p)
    {
        name = n;
        power = p;
        System.out.println(name + " 戦闘力" + power + "のサイヤ人を作成しました。");
    }

    public void show()
    {
        System.out.println("サイヤ人の名前は" + name + "です。");
        System.out.println("戦闘力は" + power + "です。");
    }
}

class NamekianWarrior implements iWarrior
{
    private String name;
    private String specialSkill;

    public NamekianWarrior(String n, String sk)
    {
        name = n;
        specialSkill = sk;
        System.out.println(name + " 特技" + specialSkill + "のナメック星人戦士を作成しました。");
    }

    public void show()
    {
        System.out.println("ナメック星人戦士の名前は" + name + "です。");
        System.out.println("特技は" + specialSkill + "です。");
    }
}

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

        warriors[0] = new Saiyan("ベジータ", 18000);
        warriors[1] = new NamekianWarrior("ピッコロ", "魔貫光殺砲");

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

実行結果

ベジータ 戦闘力18000のサイヤ人を作成しました。
ピッコロ 特技魔貫光殺砲のナメック星人戦士を作成しました。
サイヤ人の名前はベジータです。
戦闘力は18000です。
ナメック星人戦士の名前はピッコロです。
特技は魔貫光殺砲です。

このプログラムで見てほしいところ

まず、iWarrior には show() しか書かれていません。

interface iWarrior
{
    void show();
}

ここでは、「戦士なら show を持つ」という約束だけを定めています。
まだ中身はありません。

そのあとで、

  • Saiyan
  • NamekianWarrior

の両方が implements iWarrior と書き、自分の show() を定義しています。

つまり、

  • インターフェイスは約束だけを決める
  • 実際の処理はクラスが引き受ける

という役割分担になっています。

Saiyan と NamekianWarrior の show() は何が違うのか

どちらのクラスも show() を持っていますが、中身は違います。

クラスshow() の内容
Saiyan名前、戦闘力を表示する
NamekianWarrior名前、特技を表示する

ここがとても大切です。

インターフェイスは「show を持つ」という約束だけをしています。
だから、どんな内容の show にするかは各クラスに任されています。

ドラゴンボールでいえば、

  • サイヤ人なら戦闘力を見せる
  • ナメック星人戦士なら特技を見せる

という違いです。

でもどちらも show() という同じ名前のメソッドを持っているので、呼び出す側は同じ形で扱えます。

インターフェイス型の配列でまとめて扱えるのが大きな利点

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

iWarrior[] warriors;

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

  • Saiyan
  • NamekianWarrior

を同じ配列でまとめて扱えます。

そのうえでループの中では、ただ

warriors[i].show();

と呼び出しているだけです。

それでも、

  • サイヤ人ならサイヤ人の show
  • ナメック星人戦士ならナメック星人戦士の show

が、それぞれ適切に動きます。

ここにインターフェイスの強さがあります。
クラスの中身が違っていても、共通の約束を持っているなら、同じ型としてまとめて扱える のです。

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

インターフェイスは抽象クラスと同じような働きを持ちます。
実際、どちらもサブクラスをまとめて扱うためにとても役立ちます。

ただし、違いもあります。

比較項目抽象クラスインターフェイス
共通の通常フィールド持てる原則として持てない
フィールドの値変更できる原則できない
通常メソッド持てる原則として処理を書かない
コンストラクタ持てる持てない
役割共通の土台共通の約束

ドラゴンボール風に言いかえるなら、

  • 抽象クラスは「戦士としての共通の土台」
  • インターフェイスは「戦士が必ず守る約束」

です。

抽象クラスは、たとえば速度のような共通フィールドを持たせられます。
一方でインターフェイスは、speed のような変更可能なフィールドは持たせられません。
ここが、かなり大きな違いです。

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

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

今回なら、iWarrior を見ただけで

  • この型に属するものは show を必ず持つ

とわかります。

そして、その約束を守っているクラスは、どれも iWarrior 型としてまとめて扱えます。
だから、呼び出す側は細かいクラスの違いを気にせず、同じ show() で整理して書けます。

ドラゴンボールでたとえるなら、

  • 戦士の見た目や能力は違っていても
  • iWarrior を実装しているなら
  • 必ず show を使って情報を見せられる

ということです。

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

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

スーパークラス・抽象クラス・インターフェイスの変数を使えば、それらの機能を継承するサブクラスのオブジェクトをまとめて扱うことができます。

これはオブジェクト指向らしい、とても大切な考え方です。

ドラゴンボールで考えると、

  • サイヤ人
  • ナメック星人戦士

は別々のクラスです。
でも「戦士として show を持つ」という共通の見方をすると、まとめて扱えます。

現実の世界でも、ものごとは「何者か」と「何ができるか」の両方で整理できます。
インターフェイスは、そのうち特に「何ができるか」をきれいに表現するためのしくみです。

図でインターフェイスのしくみを整理する

上部の iWarriorインターフェイス は、show() という共通の約束だけを持っています。
ここには処理の中身はなく、「このメソッドを必ず持つ」というルールだけがあります。

その下の Saiyanクラス と NamekianWarriorクラス は、その約束を引き受けて show() を実際に定義しています。
だから、両方とも iWarrior 型として扱えます。

さらに下部では、iWarrior[] の配列で両方のオブジェクトをまとめて扱いながら、show() を同じ形で呼び出せることを表しています。
ここから、インターフェイスが「約束を共通化し、実装はクラスごとに任せる」仕組みであることが見えてきます。

ドラゴンボールで感覚的に整理する

最後に、ドラゴンボールの感覚でまとめてみましょう。

インターフェイスは、戦士の「能力の約束」を表すしくみです。

  • iWarrior を実装しているなら、必ず show を持つ
  • でも show の中身は、サイヤ人とナメック星人戦士で違ってよい
  • だから、違うクラスでも iWarrior としてまとめて扱える

ということです。

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

この感覚がつかめると、抽象クラスとの違いも見えやすくなり、オブジェクト指向の設計がぐっと整理しやすくなります。