8. インタフェース

8.1. インタフェース

クラスに対する操作 (メソッド) の集合をインタフェースと言います。単純なインタフェースの例を以下に示します。

(例) 単純なインタフェース - Runnable

public interface Runnable {
    void run();
}

一般には、インタフェースは以下のように定義されます。

package package_name ;

[ import package_name . { ClassName | * } ; [ import ... ; ] ... ]

// InterfaceName : インタフェース名
[ public ] interface InterfaceName {

    // static フィールド定義
    [ static_field [ static_field ... ] ]

    // static メソッド定義
    [ static_method [ static_method ... ] ]

    // メソッドのデフォルト実装
    [ default_method [ default_method ... ] ]

    // 抽象メソッド定義
    [ abstract_method [ abstract_method ... ] ]
}

インタフェースは抽象クラスと類似点が多く見られます。

  • それ自体のインスタンスを生成できない。
  • 抽象メソッドを持つことが可能で、サブクラスまたは実装クラスでメソッドをオーバーライド (定義) できる。
  • static フィールドと static メソッドについては定義できる。
  • データ型を指定する局面で、具象クラスの代わりに用いることができる。このドキュメントで「データ型にはクラスを指定できる」旨の記述がある場合、抽象クラスまたはインタフェースも「クラス」に含まれる。

その一方で、インタフェースと抽象クラスには以下の相違点があります。

  • インタフェースは通常のフィールド、メソッドを定義できない。抽象クラスでは定義できる。
  • インタフェースはコンストラクタを定義できない (デフォルト・コンストラクタは存在するが、あまり意味を持たない)。抽象クラスはコンストラクタを定義できる。
  • インタフェースではすべての抽象メソッド、static フィールド、static メソッドは public スコープとなる。抽象クラスにはそのような制限はない。
  • インタフェースは複数のスーパーインタフェースを継承できる。抽象メソッドは複数のインタフェースを実装することは可能だが、継承できるスーパーインタフェースは 1 つに限られる。
  • インタフェースは抽象メソッドのデフォルト実装を持つことができる (言語仕様上の制約があるため注意すること)。抽象クラスはデフォルト実装を持つことはできない (通常のメソッド定義が行えるため)。

インタフェースの static メソッド、およびデフォルト実装は Java SE 8 で追加された機能です。

8.2. インタフェースの実装

インタフェースは、クラスで「実装」することにより初めて利用することができます。以下にインタフェースを実装するクラスの一般的な書式を示します。インタフェースを実装するクラスは同時にクラスの継承を行っても良く、また抽象クラスとして定義することも可能です。なお、抽象クラスとして定義した場合は、具象クラスであるサブクラスでインスタンスを生成する必要があります。

// ClassName : クラス名、SuperclassName : スーパークラス名、InterfaceName : インタフェース名
[ public ] [ abstract | final ] class ClassName [ extends SuperclassName ] implements InterfaceName {
    ...
}

先に示した Runnable インタフェースを実装した例を以下に示します。

// Runnable インタフェースを実装した MyRunnable クラスを定義する
public class MyRunnable implements Runnable {
    public void run() {  // メソッドのスコープは必ず public になる
        System.out.println("Hello, world");
    }
}

クラスで複数のインタフェースを実装する場合に、抽象メソッドの定義が重複することもありますが問題ありません。最終的な具象クラスでメソッドの実装が 1 つ存在していれば良いためです。同様の理由で実装するインタフェースの抽象メソッドと、継承するスーパークラスのメソッドが重複しても構いません。実例を挙げると、Java SE 標準 API に含まれる CharSequence インタフェースは Object クラスと toString メソッドが重複します。

インタフェースはそれを実装した具象クラスに対してインタフェース経由でアクセスできます。例えば、String クラスは以下に示すように SerializableComparableCharSequence の 3 つのインタフェースを同時に実装しており、各インタフェースのメソッドからの操作が可能となっています。

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

Comparable インタフェースは comapareTo メソッドによる比較をサポートします。また、CharSequence インタフェースは StringBuilder 等と共通する基本的な文字列操作メソッドを提供します。

Serializable インタフェースは、クラスが直列化可能 (インスタンスをネットワーク越しに利用するための一時保存・復元に対応している) であることを示すもので、以下のように中身のないインタフェースとして定義されます。

public interface Serializable {
}

Serializable インタフェースのように、中身を持たず目印として使用されるインタフェースを「マーカー・インタフェース」と呼びます。最近の Java ではマーカー・インタフェースの代わりにアノテーションを使用する傾向にあります。

インタフェースは Java でポリモーフィズムを実現するための重要な機能であると同時に、後述のラムダ式とも密接な関連があるため、必ず使い方を覚えてください。

8.3. インタフェースと匿名クラス

インタフェースを用いてメソッド内でクラスの実装を行う「匿名クラス」という構文があります。以下に匿名クラスによるインスタンス生成を行って、それを別スレッドで非同期実行する例を示します。

// 匿名クラスによるインスタンス生成
Runnable runnable = new Runnable() {
    public void run() {
        // 非同期実行すべき処理
        ...
    }
};

// runnable を別スレッドで非同期実行する
new Thread(runnable).start();
...

上記の例はメソッド本文で匿名クラスを使用しましたが、以下のように呼び出すメソッドの引数でも匿名クラスを使用することができます。

// Runnable の匿名クラスでインスタンスを生成し、別スレッドで非同期実行する
new Thread(new Runnable() {
    public void run() {
        // 非同期実行すべき処理
        ...
    }
} ).start();

一般に、匿名クラスは以下の書式で使用します。

// InterfaceName : インタフェース名、var : 変数名
[ InterfaceName var = ] new InterfaceName () {
    // メソッド定義 (すべての抽象メソッドを実装すること)
    [ method [ method ... ] ]
}

匿名クラスを使用したインスタンス生成はインタフェースだけでなく抽象メソッドでも行うことができます。詳細については割愛しますが、抽象メソッドから匿名クラスを作成する場合は抽象クラスの実装は必須ですが、必要に応じて既存のメソッドのオーバーライドも可能になります。

Java のメソッドでは引数にデータ (変数・フィールドの値) のみ設定可能で、処理を渡すことができません。しかし、メソッド側では処理の入出力だけを決めておき、実際の処理はメソッドの外部で定義したいケースがあります。代表例がフォルダ階層をスキャンしながら行う処理で、フォルダのスキャン手順はあらかじめ決めておくことができますが、スキャンとともに行う処理までは決められません。このような問題に対処するテクニックである「Visitor パターン」でよく利用されるものが匿名クラスです。「Visitor パターン」で処理を定義するクラスは小規模なものが多く、都度インタフェースまたは抽象クラスから具象クラスを作成していては手間がかかってしまうことから、匿名クラスが多用されています。

匿名クラスでは、外側の変数を参照することもできますが、それらを変更することはできません。匿名クラスから外側の変数を変更するコードはコンパイルエラーとなります。必須ではありませんが、匿名クラスから参照する外側の変数は安全のため final を付加しておくとよいでしょう。

Java SE 7 までは、匿名クラスから参照する外側の変数は final が必須でした。現在では final を付加しなくてもエラーにはなりませんが (「実質的 final」といいます)、外側の変数を変更しようとするコードがコンパイルエラーとなるのは変わりがありません。

8.4. インタフェースのデフォルト実装

インタフェースを実装するクラスでは、すべての抽象メソッドを実装しなければなりません。あるインタフェースの提供後に抽象メソッドを追加する必要が出てきたとき、その変更は当該インタフェースを実装するすべてのクラスに波及します。Java 標準 API では様々な手段を講じてこの問題を回避してきましたが、Java SE 8 に至っていよいよ回避できない事態に陥りました。そこで考案されたのがインタフェースのデフォルト実装です。

デフォルト実装は、これまで処理を記述できなかった抽象メソッドに一定の条件を付けて処理の記述を許可したもので、デフォルト実装のあるメソッドについてはクラスでの実装が任意となります。インタフェースにメソッドを追加する必要が生じても、デフォルト実装を記述することでクラス側での対応 (抽象メソッドの実装) が不要となります。

インタフェースのメソッドのデフォルト実装は、一般的に以下の書式となります。

// method : メソッド名、arg : 仮引数、ReturnType : 戻り値の型
default ReturnType method ( [ arg [, arg ... ] ] ) {
    ...
}

デフォルト実装にはフィールドにアクセスするようなコードは記述できません。デフォルト実装が存在するのはインタフェースであり、インタフェースはフィールドを持たないためです。一方で static メソッド、static フィールド、抽象メソッドにはアクセスできます。

デフォルト実装はクラスでいつでもオーバーライド可能で、オーバーライドした後は必ずクラスで定義したメソッドが呼び出されます。デフォルト実装をオーバーライドすると通常のメソッドになり、フィールドへのアクセスもできるようになります。

ただし、同時に実装するインタフェースでメソッドのデフォルト実装が重複している場合には、デフォルト実装を呼び出すことができません。そのような場合にはクラスで必ずオーバーライドする必要があります (デフォルト実装が重複していても、オーバーライドした後は必ずクラスで定義したメソッドが呼び出されます)。

インタフェースのデフォルト実装は当初 Java API の互換性維持のために導入されたものですが、Java SE 8 の開発中に API 仕様がブラッシュアップされるにつれて、単なる互換性維持に留まらない、広い範囲で使用されるようになりました。ある程度は意識しておいた方が良いでしょう。

results matching ""

    No results matching ""