Print Friendly, PDF & Email

新人エンジニア研修で知っておきたいインターフェースの使い方

なぜ、インターフェースの理解が重要なのか、その理由。

この記事では、弊社の新人エンジニア研修の参考にJava8を解説します。

前回は抽象クラスについて解説しました。

今回はインターフェースについて解説します。

インターフェースが何のためにあるのか?を一言で言えばポリモーフィズムのためです

まずはそのことを念頭においてください。

また、新人エンジニア研修でみなさんがインターフェースを作ることはありません。

ただし、利用することはありますのでその仕組を知ることは重要です。

インターフェースも抽象クラス同様インスタンス化できません。

しかし、抽象クラスとはまた違った意味で巧妙な役割が与えられています。

インターフェースを考える際にはJavaでは多重継承が禁じられていたことを思い出す必要があります。

Javaではスーパークラスを継承するサブクラスの数に制限はありませんでした。

しかし、サブクラスから見て直接のスーパークラスは一つだけと決まっていました。

これがJavaでは多重継承が禁じられているということの意味です。

では、継承関係にかかわらずポリモーフィズムを使いたいときにはどうしたらいいのでしょうか?

そうです。

インターフェースを使います。

そもそもinterfaceとは接点のことです。

人間とコンピュータとの接点をman - machine interface と言ったりしますね。

あるいは、パソコンと周辺装置のインターフェースという表現もあります。

例えば、USBというインターフェースを例にとりましょう。

USBの接続口がある製品は何でも、パソコンでもプリンタでもスキャナーでも相互に接続できます。

なぜなら、お互いに一定のルールを内包して共有しているからです。

インターフェースのイメージ
インターフェースのイメージ

Javaのインターフェースのイメージもこのルールを内包して共有するというところにあります。

同じインターフェースを組み込むことでクラスを共通ルールのもとに扱うことができるようになるのです。

1.Comparableインターフェース

例によりJava8の標準APIに使用例の範を求めましょう。

前回も使ったIntegerクラスに再登場いただきましょう。

IDEでIntegerクラスの52行目の宣言部分を見てみます。

public final class Integer extends Number implements Comparable<Integer> {
  以下省略

implements Comparable」という文字列が見えます。

この部分がインターフェースを実装している部分です。

インターフェースを実装するクラス宣言にはimplementsと書きます

英語のimplementsには実装という意味があります。

ちょうどアタッチメントなどを付ける感覚です。

Comparableの文字列の先頭文字が大文字になっています。

クラス同様インターフェースも先頭は大文字です。

compare「比較」にable「できる」という意味の接尾語がついています。

比較できるためのインターフェースを実装しているわけですね。

比較できるというルールを内包して、共有しているわけです。

後ろの<Integer>というのはジェネリックス(Generics:総称型)というものです。

詳しくはあとで章を改めてお話しますが、「比較の対象はIntegerクラスのインスタンスだけですよ」とあらかじめ断っているのです。

※ちなみにクラスとインターフェースを名前だけで判別する際に接尾語に注目してableがついていたらインターフェースと考えると良いでしょう。
インターフェースを実装するということは、能力を組み込むということだと考えるとナイスなネーミングですね。
ただし、すべてのインターフェースにableがついているわけではないので誤解のないように。

では、次にComparable(標準API)から検索してみます。

するとこのように書かれています。

このインタフェースを実装する各クラスのオブジェクトに全体順序付けを強制します。

「全体順序付けを強制します」とあります。

整数値にせよ、文字列にせよ、インスタンスを順序付けできないと並べ替え等ができませんからこれは大切なインターフェースですね。

しかし、equalsメソッドのところでも似たような議論がありましたが、何をもって順序を前、後ろと決めるのかという問題があります。

例えば、この新入社員研修のクラス一つとってみても、順序付けの基準は無限に考えられます。

名前のあいうえお順、身長順、年齢順、成績順、などなど。

そこで、Comparableインターフェースでは、「比較する」という抽象メソッドのみを決めて、実装は個々のクラスに任せるのです。

IDEを使ってComparableインターフェースの中身を見ていくと一番下、136行目に以下のような記述があります。

public int compareTo(T o);

※()の中のT oもジェネリックスです。Tはタイプ(型)の頭文字で任意のクラスの型を意味しています。

いつものメソッドについている()の後ろの{}がありません。

そうです。中身のない抽象メソッドなのです。

このとき、抽象クラスの時に学んだabstractキーワードがついていないことに着目してください。

インターフェースでは、メソッドは基本、抽象メソッドになります。

※実は、Java8からdefaultメソッドというものも使えるようになったのですが、本研修では触れません。

また、仮にpublicがついていなくてもpublicなメソッドになります。

インターフェースに定める抽象メソッドは暗黙的にpulblicで修飾されるのです。

なぜなら、インターフェースは外部に公開することを目的としているものなので、そもそも公開を制限するのは論理矛盾なのです。

Comparableインターフェースを実装したクラスでは比較できるという性質を具体的に記述することが必要です。

では、このcompareToメソッドをオーバーライドしている個所をIntegerクラスの1215行目に見つけてください。

    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }

compareToメソッドの中でcompareメソッドを呼んでいます。

次はこのcompareメソッドに飛んでください。すぐ下の1233行目にあります。

   public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }

compareメソッドの中身の三項演算子はxとyを比較して

yが大きければ-1を返す

同じであれば0を返す

xが大きければ1を返す

ということをしているだけです。

では、そのことを確認するサンプルプログラムを書いてみましょう。

package chap13;

public class Example01 {

    public static void main(String[] args) {
        Integer one = 1;
        Integer two = 2;
        Integer three = 3;
        System.out.println(two.compareTo(one));
        System.out.println(two.compareTo(two));
        System.out.println(two.compareTo(three));
    }
}

<結果を表示する>

1
0
-1

確かにそうなっていますね。

ですので、(実用性はともかく)以下のように最大値を求めるプログラムが書けます。

cという変数名で宣言されている配列の型に注目してください。

package chap13;

public class Example02 {

    public static void main(String[] args) {
        Comparable[] c = {new Integer(10), new Integer(20), new Integer(1500), new Integer(202)};
        Comparable max = c[0];
        for (int i = 0; i < c.length - 1; i++) {
            if (max.compareTo(c[i + 1]) < 0) {
                max = c[i + 1];
            }
        }
        System.out.println(max);
    }
}

Comparable型の配列を宣言しています。

つまり、この配列にはComparableインターフェースを実装しているクラスのインスタンスであればなんでも入れることができます。

インターフェースとそのインターフェースを実装したクラスの関係は“is-a”関係です。

パソコン is a コンピュータ

サブクラス is a スーパークラス

といったような論理的な意味関係は以下のように失われてしまっています。

Integer is a Comparable.

実装クラス is a インターフェース.

とは言えませんね。

しかし、こうすることで後で説明するようにポリモーフィズムを働かせることが可能になります。

2.インターフェースのメソッドをオーバーライドする

Comparableインターフェースを活用したプログラムの実例として、以下のようなStudentの年齢の最大値を求めるコードを作成してみました。

オーバーライドしたcompareToメソッドの実装方法に気をつけて以下のようなコードを読んでみてください。

package chap13;

public class Student implements Comparable {

    int age;
    int height;

    public Student(int age, int height) {
        this.age = age;
        this.height = height;
    }

    @Override
    public int compareTo(Object anotherStudent) {
        Student as = (Student) anotherStudent;
        return this.age - as.age;
    }

    @Override
    public String toString() {
        return "Student{" + "年齢=" + age + ", 身長=" + height + '}';
    }
}
package chap13;

public class Example03 {

    public static void main(String[] args) {
        Comparable[] c = {new Student(29, 173), new Student(33, 169), new Student(30, 180), new Student(25, 175)};
        Student max =  (Student)c[0];
        for (int i = 0; i < c.length - 1; i++) {
            if (max.compareTo(c[i + 1]) < 0) {
                max = (Student) c[i + 1];
            }
        }
        System.out.println(max);
    }
}

<結果を表示する>

Student{年齢=33, 身長=169}

戻り値は-1,0,1に限定されず、2つの値の大小がプラスマイナスで戻ればよいため、

return this.age - as.age; 

とシンプルに書いていますが、これでOKです。

インターフェースを実装したときのクラス図も載せておきます。

インターフェースのクラス図を新人エンジニア研修で説明
インターフェースのクラス図

継承のクラス図との相違点を意識してください。

親クラスの継承では実線でしたが、インタフェースの実装は点線になります。

また、インタフェースには、<<interface>>というステレオタイプという表記が付きます。

ちなみに、抽象メソッドをオーバーライドする利点として、全てのメソッドの名前が同じになるので、あえてドキュメントを読まなくてもそのメソッドの挙動が推測できるというものがあります。

この点は研修が進むとだんだん実感できると思います。

では、一番身長の高い人を見つけるにはどこをどのように変更すればよいでしょうか?

試してみてください。

※ちなみに、equalsメソッドがObjectクラスのメソッドをオーバーライドして使うものだったのに対して、CompareToメソッドはComparableインターフェースを実装して使うというところにこの2つの処理の適用範囲が表れていて興味深いですね。つまり、等しいかどうかというのはすべてのクラスに共通の問題ですが、順序付けに関してはそれを必要としないクラスもあるという。

3.インターフェースを使ったポリモーフィズム

インターフェースを使ってポリモーフィズムを実現することができます。

サブクラスは一種のスーパークラスだったので、スーパークラスの変数にサブクラスのインスタンスを参照させることができました。

同じように、インターフェース型の参照変数にインターフェースを実装したクラスのインスタンスを代入することができるのです

目的は継承の時と同様に、ポリモーフィズムです。

変数の仮引数で大は小を兼ねたいときに使います。

例えば、人間クラスと猫クラスのインスタンスが混在している配列から年齢の一番高いインスタンスを見つけます。

ただし、直接人間と猫のようにクラスが違うものをcompareToメソッドで比較するとキャストが頻発して複雑で変更に弱いプログラムになってしまいます。

ここでは、人間と猫の共通のクラスAnimalを用意してそこにcompareToメソッドを実装しています。

package chap13;

public class Example04 {

    public static void main(String[] args) {
        Comparable[] c = {new Cat(21, 63), new Human(33, 169), new Cat(29, 55), new Human(25, 175)};
        Comparable max = c[0];
        for (int i = 0; i < c.length - 1; i++) {
            if (max.compareTo(c[i + 1]) < 0) {
                max = c[i + 1];
            }
        }
        System.out.println(max);
    }
}

abstract class Animal implements Comparable {

    int age;
    int height;

    public Animal(int age, int height) {
        this.age = age;
        this.height = height;
    }

    @Override
    public int compareTo(Object anotherAnimal) {
        Animal a = (Animal) anotherAnimal;
        return (this.age < a.age) ? -1 : ((this.age == a.age) ? 0 : 1);
    }

    @Override
    public String toString() {
        return this.getClass() + "{ 年齢=" + age + ", 身長=" + height + '}';
    }
}

class Cat extends Animal {

    public Cat(int age, int height) {
        super(age, height);
    }

    public void speak() {
        System.out.println("mew mew mew");
    }
}

class Human extends Animal {

    public Human(int age, int height) {
        super(age, height);
    }

    public void speak() {
        System.out.println("blah blah blah");
    }
}

※getClassメソッドはクラス名を取得するメソッドです。

※繰り返しになりますが、複数のクラスを1つのファイルに書く書き方は実務では推奨しません。

<結果を表示する>

class chap13.Human{ 年齢=33, 身長=169}

以下にクラス図を示します。

インターフェース型の参照変数にインターフェースを実装したクラスのインスタンスを代入することができることを新人エンジニア研修で説明
インターフェース型の参照変数の使いどころ

こうしておけば犬クラスやキリンクラスが追加されても同様に扱うことができます。

4.複数インターフェースの実装

複数インターフェースを実装している例を見てみましょう。

JavaのソースコードでStringクラス(標準API)を見てみます。

すると111行目に次のように定義されています。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

このようにimplementsの後に,(カンマ)で区切って複数のインターフェースを実装することができます

Comparableインターフェースは既にみました。

SerializableCharSequenceというインターフェースは初出ですね。

Serializable インターフェースはマーカーインターフェースといって、marker:目印としての意味だけの特殊なインターフェースです。

Serializable インターフェース のソースコード169行目を見ても中身は空です。

public interface Serializable {
}

Serializable は中身は空ですが、ObjectOutputStream に利用できる、つまりディスクに保存したりネットワーク経由で送受信できるという特性を持っていることを示しています。

この場合のserial(連続)とは直列化ともいい、 0,1の2進数の形にすることを意味します。

この後、Webアプリケーションを学ぶ方は、後でJavaBeansというテーマで再会しますので心に留めておいてください。

次に、標準APIでCharSequenceというインターフェースを追いかけてみます。

既知のすべての実装クラス:CharBufferSegmentStringStringBufferStringBuilder

文字と文字列のところで少し紹介したStringBuilderクラスがCharSequenceインターフェースを実装しているようです。

どのような意図があるのでしょうか?

StringクラスもStringBuilderクラスもCharSequenceインターフェースを実装しているということは、どちらのクラスもCharSequenceインターフェース型の変数で扱えるということです。

ですから、メソッドを作成するときに仮引数にCharSequence型を宣言しておけば、実引数としてはStringのインスタンスもStringBuilderのインスタンスも受け取ることができるのです。

わざわざオーバーロードしなくても良いのです。

サンプルコードを掲載しておきます。

package chap13;

public class Example05 {

    static void show(CharSequence cs) {
        System.out.println(cs);
    }

    public static void main(String[] args) {

        String str = "Hello World.";
        StringBuilder strb = new StringBuilder("Goodby World.");

        show(str);
        show(strb);
    }
}

<結果を表示する>

Hello World.
Goodby World.

今回はインターフェースについて見てきました。

インターフェースを使うことで、Java8でも実質的には多重継承が可能となります。

※多重実現と表現することがあります。

より、柔軟なクラス体系を作り出すことができるということが理解できたのではないでしょうか?

また、抽象クラスかインターフェースかどちらを選択するかという点で言うと、インターフェースに軍配が上がることはこれまでお読みいただいた皆さんには理解いただけるものと思います。

次回のテーマは例外処理です。

例外処理を使って、途中で落ちてしまわない頑強なプログラムを作っていきましょう。

<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

インターフェースの存在理由はポリモーフィズムのためである  

インターフェースを実装するクラス宣言にはimplementsと書く  

□インターフェースでは、メソッドは基本、抽象メソッドになるので実装クラスでオーバーライドする  

□インターフェース型の参照変数にインターフェースを実装したクラスのインスタンスを代入することができる  

□implementsの後に,で区切って複数のインターフェースを実装することができる

まとめができたら、アウトプットとして演習問題にチャレンジしましょう。

問題13.インターフェース

インターフェースに関する問題です。 1.インターフェースの作成 次のアウトプットとソースコードに整合するようにインターフェース、IPhone,IBrouser,ICamera,IMusicPlaye…

【今回の復習Youtube】

044-インターフェース-インターフェースの定義と実装

045-インターフェース-インターフェースとポリモーフィズム

046-インターフェース-インターフェースの配列

JavaSE8の解説に戻る

最後までお読みいただきありがとうございました。

新人エンジニア研修のプログラムを見てみる

当社は研修講師を募集中です(サブ講師のため未経験者可)