第27講・STEP 04

インターフェースって何? 抽象クラスとの違いは?

インターフェースって何なのかイマイチよく分からない。そんな人に向けて、その概念や使い方、使いどころを説明します。「飛べる人間」と「クリックできるビュー」を例に、共通の呼び出し方を決める仕組みを見ていきましょう。

インターフェースと継承

 インターフェースを理解するには、先に継承や抽象クラス(abstract)について理解しておいた方がいいです。まだよく分からない人は、継承って何?やabstractって何?から読み直してみてください。

 今までクラスを書いてきましたが、クラスというのは何らかのデータや振る舞いを持つオブジェクトを作るために書くのでした。「食事をできる人間」だとか、「走れる車」だとか、あくまで人間であったり車であったり、何らかのオブジェクトの設計図になるのがクラスです。

 そのクラスを元に、より限定的(専門的)なクラスを作るのが継承です。「プログラミングができる人間(プログラマー)」のように、親の特徴を引き継ぎながら独自の特徴を加えられます。

 それに対し、インターフェースは、言わば「何ができるか」という約束を取り出したものです。「飛べる」「クリックできる」のような役割を、クラスの種類とは別に定義できます。

 継承にはextendsを使いましたね。インターフェースをクラスに実装する時は、implementsを使います。

Human.java

class Human implements Flyable {
    // fly()の処理をここに書く
}

 ↑HumanクラスにFlyableインターフェースを実装する、という宣言です。Flyableは「飛べる」という意味です。インターフェースには、このように動詞+ableという名前を付けることがあります。具体的なfly()の書き方は、すぐ後で説明します。

 継承との大きな違いとして、一つのクラスに複数のインターフェースを実装できます。名前をコンマ区切りで並べるだけです。

Human.java

class Human implements Flyable, Swimmable {
    // 各インターフェースで求められるメソッドを実装する
}

 もしSwimmableに「泳げる」という約束を定義しておけば、飛べて泳げる人間を表せます。継承と実装を組み合わせることもできます。

SuperCar.java

class SuperCar extends Car implements Flyable {
    // fly()の処理をここに書く
}

 こちらはCarを継承しつつ、Flyableを実装する「空を飛べる車」です。

インターフェースの定義の仕方

 インターフェースの定義は、クラスとよく似ています。classの代わりにinterfaceと書きます。ここからの例は、各ファイルを同じパッケージに置いてください。

Flyable.java

interface Flyable {
    void fly();
}

 Flyableのメンバはflyメソッドのみです。しかも、処理の中身はありません。この形、見覚えありますよね? 前回出てきた抽象メソッドです。

 このように宣言したメソッドは、public abstractを省略しても、その二つが付いているものとして扱われます。ここでは「fly()という呼び出し方」を決め、具体的にどう飛ぶかは実装先のクラスに任せます。

 要するに「Flyableを実装するなら、fly()を呼ばれた時の処理を用意しておきなさいよ」という約束です。Humanクラスに実装してみましょう。

Human.java

class Human implements Flyable {
    String name;

    Human(String name) {
        this.name = name;
    }

    @Override
    public void fly() {
        System.out.println(this.name + "は空高く飛んだ!");
    }
}

 fly()にはpublicを付けます。インターフェース側のfly()がpublicなので、実装側でアクセス範囲を狭くすることはできないからです。

 では、実際にHumanオブジェクトを飛ばしてみましょう。

Main.java

public class Main {
    public static void main(String[] args) {
        Human human = new Human("Nobuo");
        human.fly();
    }
}
実行結果
Nobuoは空高く飛んだ!

 飛びました。

 先ほどインターフェースは「何ができるか」を取り出したものと言いましたが、具体的な処理はHuman側に書いています。インターフェースは、まずは共通の呼び出し方の枠を提供するものだと思ってください。

 また、Flyableは変数の型としても使えます。先ほどのmainメソッドの中を、次のように書き換えても同じ結果になります。

Main.java

Flyable human = new Human("Nobuo");
human.fly();

 HumanがFlyableを実装しているので、そのインスタンスをFlyable型の変数で扱えます。Flyableにfly()が宣言されているから、Flyable型からも呼び出せるわけです。ただし、new Flyable()のようにインターフェースそのものを直接インスタンス化することはできません。

 まあ、Nobuoが飛んだと言われても使いどころがイマイチ分からないと思うので、もう少し実践的な例で説明します。

ポリモーフィズムとは?

 インターフェースの使いどころは、ポリモーフィズム(多態性)という概念とセットで考えると分かりやすいです。言葉だけではチンプンカンプンだと思うので、例を見ていきましょう。

 アプリの画面に配置するパーツを「ビュー」と呼ぶことにします。幅と高さを持つViewクラスを、抽象クラスとして用意します。

View.java

abstract class View {
    int width;
    int height;

    View(int width, int height) {
        this.width = width;
        this.height = height;
    }
}

 このViewを継承して、文字を表示するTextViewと、画像を表示するImageViewを用意します。まずはTextViewです。

TextView.java

class TextView extends View {
    String text;
    int textSize;

    TextView(int width, int height, String text) {
        super(width, height);
        this.text = text;
        this.textSize = 10;
    }
}

 文字列と文字のサイズを持たせました。ImageViewには、画像の場所を表す文字列を持たせます。

ImageView.java

class ImageView extends View {
    String src;

    ImageView(int width, int height, String src) {
        super(width, height);
        this.src = src;
    }
}

 ところで、ビューにはクリックすると反応するものと、そうでないものがありますね。「クリックされた時の処理を持つ」という役割を、Clickableインターフェースで定義しましょう。

Clickable.java

interface Clickable {
    void onClick();
}

 クリックして反応するものと言えば、ボタンです。文字を表示するボタンはTextViewを継承しつつ、Clickableを実装すれば表せます。

ButtonView.java

class ButtonView extends TextView implements Clickable {
    ButtonView(int width, int height, String text) {
        super(width, height, text);
    }

    @Override
    public void onClick() {
        System.out.println(this.text + "がクリックされました。");
    }
}

 文字ではなく画像のボタンを作りたいなら、ImageViewを継承して、同じくClickableを実装します。

ImageButtonView.java

class ImageButtonView extends ImageView implements Clickable {
    ImageButtonView(int width, int height, String src) {
        super(width, height, src);
    }

    @Override
    public void onClick() {
        System.out.println(this.src + "の画像ボタンがクリックされました。");
    }
}

 それぞれ継承するクラスは違いますが、どちらもClickableです。では、呼ぶ側のコードを書いてみましょう。次のMainは、先ほどのHumanを使うMainとは置き換えてください。

Main.java

public class Main {
    public static void main(String[] args) {
        Clickable[] buttons = {
            new ButtonView(120, 40, "送信"),
            new ImageButtonView(48, 48, "home.png")
        };

        for (int i = 0; i < buttons.length; i++) {
            buttons[i].onClick();
        }
    }
}
実行結果
送信がクリックされました。
home.pngの画像ボタンがクリックされました。

 ここではクリックを検知する代わりに、mainから順番にonClick()を呼んでいます。画像を読み込む処理もありませんので、home.pngという実際のファイルを用意する必要はありません。

 注目してほしいのは、呼ぶ側が文字のボタンなのか画像のボタンなのかを区別していないことです。どちらもClickable型として扱い、同じonClick()で呼び出しています。それでも、実行される処理は各クラスで定義したものになります。

 このように、共通の型と呼び出し方で扱いながら、実際のオブジェクトに応じた処理が実行されることをポリモーフィズム(多態性)と言います。

 新しい種類のボタンを追加しても、Clickableを実装していれば、このループの処理はそのまま使えます。呼ぶ側は「クリックできるもの」という共通の約束だけを知っていればいいわけです。

 抽象クラスやインターフェースは、この多態性を使いやすくするための仕組みです。多態性自体は、通常のクラスの継承とメソッドのオーバーライドでも実現できます。

インターフェースと抽象クラス

 インターフェースと抽象クラスは、共通の呼び出し方を決め、具体的な処理を別のクラスに任せられる点が似ています。一方で、違う点もあります。整理しておきましょう。

アクセス修飾子

 まず、型そのもののアクセス修飾子と、中に書くメンバのアクセス修飾子は分けて考えます。

 今回のようにファイル直下に宣言するインターフェースも抽象クラスも、publicなら別のパッケージから利用できます。アクセス修飾子を付けなければ、同じパッケージ内からの利用に限られます。どちらも、この位置ではprotectedやprivateは付けられません。

 インターフェースに書いたvoid fly();のような抽象メソッドはpublicです。実装するクラス側のfly()にもpublicが必要でしたね。インターフェース自体が同じパッケージ内にしか見えない場合でも、このルールは変わりません。

 抽象クラスのメンバには、通常のクラスと同様にアクセス修飾子を使えます。ただし、抽象メソッドは子クラスで実装する必要があるので、privateにはできません。

定義できるもの

 インターフェースは、インスタンスごとの変数やコンストラクタを持てません。宣言したフィールドはすべてpublic static finalとして扱われます。finalは、その変数に後から別の値を代入できない、という指定です。

 また、インターフェースには抽象メソッドだけでなく、処理本体を持つdefaultメソッドやstaticメソッドも書けます。Java 9以降では、内部で処理を共通化するためのprivateメソッドも書けます。まずは、この講で使った抽象メソッドの形を押さえておけば十分です。

 抽象クラスには、インスタンス変数、コンストラクタ、通常のメソッド、抽象メソッドなどを持たせられます。前回も触れた通り、抽象メソッドを一つも持たない抽象クラスも書けます。

複数の実装と継承

 クラスが直接継承できる親クラスは一つだけです。親が抽象クラスでも、この点は変わりません。一方で、インターフェースはいくつでも実装できます。

 だから、ButtonViewはTextViewを継承したまま、Clickableという役割を加えられました。なお、インターフェース同士ではextendsを使って、複数のインターフェースを継承することもできます。

インターフェースと抽象クラスの違いまとめ

この講で扱った主な違い
インターフェース抽象クラス
クラスから使う時implements(複数可)extends(親クラスは一つ)
定義できないものコンストラクタ、インスタンス変数、無し
直接インスタンス化できないできない

 ただ、機能的な違いにスポットを当ててインターフェースと抽象クラスの違いを議論しても、あまり本質的ではないのかなと思います。より重要なのは、それぞれが何を表現するための仕組みなのかという設計上の違いです。

 抽象クラスは、ある種類のオブジェクトに共通する状態や振る舞いをまとめたオブジェクトの共通の雛形であるのに対し、インターフェースは、オブジェクトが備える役割・能力・契約を表すものだと考えると自然です。

 したがって、複数のクラスに共通する「何者であるか」という土台を作り、その中で処理に多態性を持たせたい場合には抽象クラスを利用し、「何ができるか」という共通の役割を、クラスの継承関係とは独立して持たせたい場合にはインターフェースを利用する、と考えることができます。

全講座の目次へ戻る

質問・コメント

読み込み中

この記事についての質問や、学習して気づいたことをお寄せください。

コメントを書く

投稿フォームを準備しています。

コメントと一緒に公開される名前です。
3,000文字以内。パスワードなどの個人情報は書き込まないでください。

コメントは送信後すぐに公開されます。

この記事のコメント

旧サイトに寄せられたコメントも当時の投稿内容のまま掲載しています。現在の記事では変更している内容や、旧環境についてのやり取りも含まれます。

コメントを読み込んでいます。