Programming 3 / Java

トップに戻る

第10回 インタフェース

Javaの学習用の教材ページです。異なるクラスを共通の型で扱う仕組みを、金庫と宝石箱の実装を通して学びます。

学習目標

目次

このスライドでは扱わない内容

授業内課題では扱うため、参考文献等で確認すること。

目を通しておく内容

参考文献:

Java言語プログラミングレッスン 第3版(下) オブジェクト指向を始めよう

オブジェクト指向の基本的な考え方

オブジェクト指向では、オブジェクト同士はメッセージの送受信によってやり取りを行う。

Javaでは、実際には一方のクラスから別のクラスのメソッドを呼び出し、必要に応じて戻り値を受け取る。

つまり、オブジェクトやクラス同士の

「境界面・接点」

にあたるものがメソッドであると考えることができる。

この「境界面・接点」を インタフェース と考える。

クラスの継承とメソッド

例えば、次のクラスがあるとする。

Kinkoクラス

TaikaKinkoクラス

TaikaKinko クラスが Kinko クラスを継承している場合、

を共通のメソッドとして扱うことができる。

同様に、

も共通のメソッドとして扱うことができる。

継承関係にないクラスの場合

ここで、次の Car クラスを考える。

Carクラス

Car にも Kinko と同じ名前の

が存在する。

しかし、

Car と Kinko の間には継承関係がない。

そのため、名前が同じメソッドであっても、共通のメソッドとして扱うことができない。

無理に継承させる場合の問題

例えば Box クラスを作り、

の両方に Box を継承させれば、共通のメソッドとして扱えるようにすることはできる。

Boxクラス

しかし、このクラス構造には問題がある。

例えば、

というように、不自然な継承関係になってしまう。

インタフェースを利用する

そこで、クラスを継承させるのではなく、

インタフェース

を定義する。

例えば、

Lockableインタフェース

という2つのメソッドを持つインタフェースを定義する。

そして、

などのクラスで Lockable インタフェースを実装する。

これによって、継承関係にないクラスであっても、

lock()` と `unlock()

を共通のメソッドとして扱うことができる。

なお、TaikaKinko は Kinko を継承しているため、Kinko が Lockable を実装していれば、TaikaKinko で改めてインタフェースを実装する必要はない。

この教材では、抽象メソッドを宣言する基本形を学びます。インタフェースには default メソッドや static メソッドなど、処理を記述できるメソッドもあります。フィールドは定数となり、インスタンスごとの状態は持てません。

インタフェースとは

継承関係にないなどの無関係なクラスであっても、インタフェースを使用することで共通のメソッドを持たせることができる。

インタフェースでは、

共通のメソッドの形

を定義する。

そのため、基本的にはメソッドの中で実行する具体的な処理は記述せず、メソッドの形のみを宣言する。

また、インタフェースには次の特徴がある。

インタフェースの定義

インタフェースは interface を使用して定義する。

例:

構文例
public interface Lockable{
    boolean lock();
    boolean unlock();
}

クラスと同様に、ファイル名は

Lockable.java

とする。

抽象メソッド

インタフェース内では、

boolean lock();

のように、メソッドの具体的な処理を書かず、

メソッドの形だけを宣言する。

このようなメソッドを

抽象メソッド

という。

継承:Box → JewelBox → LockableJewelBox
実装:Kinko と LockableJewelBox → Lockable
矢印は、継承では親から子、実装ではクラスからインタフェースを表します。

今回の例では、次の構造を使用する。

Kinkoクラス

Lockable インタフェースを実装する。

Boxクラス

JewelBoxクラス

Box クラスを継承する。

LockableJewelBoxクラス

JewelBox クラスを継承する。

さらに、

Lockable

インタフェースを実装する。

持つメソッド:

Lockableインタフェース

1. 共通のメソッドを定義する

以下の6ファイルは、すべて C:\local-doc\github\pro3\10-a-1 に保存してください。フォルダがなければ作成し、各コード欄のファイル名を使用します。

Lockable.java
public interface Lockable{
    boolean lock();
    boolean unlock();
}

2. 金庫にロック機能を実装する

Kinko クラスで Lockable インタフェースを実装する。

Kinko.java
public class Kinko implements Lockable{
    boolean isLock = false;
    int deposit = 0;
 
    public Kinko(int d){
        deposit = d;
    }
 
    public boolean lock(){
        isLock = true;
        return isLock;
    }
 
    public boolean unlock(){
        isLock = false;
        return isLock;
    }
 
    void printDeposit(){
        if(isLock == true)
            System.out.println("ロックされています");
        else
            System.out.println("預金額は" + deposit + "です");
    }
}

インタフェースを実装するときは、

implements

を使用する。

また、Lockable インタフェースに定義されている

を Kinko クラスの中で実装する。

3. 箱と宝石箱を作る

Box.java
class Box{
    boolean isEmpty = true;
 
    public Box(boolean e){
        isEmpty = e;
    }
 
    public void printEmpty(){
        if(isEmpty == true)
            System.out.println("中身は空です.");
        else
            System.out.println("中に何か入っています.");
    }
}
JewelBox.java
class JewelBox extends Box{
    public JewelBox(boolean e){
        super(e);
    }
}

JewelBox は Box を継承するため、

extends

を使用する。

4. 宝石箱にロック機能を追加する

LockableJewelBox は、

という2つの関係を持つ。

LockableJewelBox.java
class LockableJewelBox extends JewelBox implements Lockable{
    boolean isLock = false;
 
    public LockableJewelBox(boolean e){
        super(e);
    }
 
    public boolean lock(){
        isLock = true;
        return isLock;
    }
 
    public boolean unlock(){
        isLock = false;
        return isLock;
    }
 
    public void printEmpty(){
        if(isLock == true)
            System.out.println("ロックされています.");
        else
            super.printEmpty();
    }
}

ここでは、

している。

printEmpty() では isLock の状態によって表示内容を変更する。

ロックされていない場合は、

super.printEmpty();

によって親クラスの printEmpty() を呼び出す。

5. 動作を確認するプログラムを作る

各クラスの動作確認を行う。

LockerTester.java
class LockerTester{
    public static void main(String[] args){
 
        // 預金1000円で金庫を生成して状態を表示
        Kinko k = new Kinko(1000);
        k.printDeposit();
 
        // 中身を入れた状態でロック付き宝石箱を生成して状態を表示
        LockableJewelBox j = new LockableJewelBox(false);
        j.printEmpty();
 
        // 2つのLockableオブジェクトの配列を作り、lockメソッドを呼ぶ
        Lockable[] l = { k, j };
 
        for(Lockable i : l)
            i.lock();
 
        // それぞれの状態を表示する
        k.printDeposit();
        j.printEmpty();
    }
}

6. コンパイルと実行

JDK の javac・java が使える Windows のコマンドプロンプトで実行します。まず、次のコマンドで C:\local-doc\github\pro3\10-a-1 に移動してください。
演習フォルダへ移動
cd /d C:\local-doc\github\pro3\10-a-1

それぞれのファイルをコンパイルする。

実行コマンド
javac Lockable.java
実行コマンド
javac Kinko.java
実行コマンド
javac Box.java
実行コマンド
javac JewelBox.java
実行コマンド
javac LockableJewelBox.java
実行コマンド
javac LockerTester.java

実行:

実行コマンド
java LockerTester

実行結果:

実行結果
預金額は1000です
中に何か入っています.
ロックされています
ロックされています.

lock() メソッドが正しく動作し、

「ロックされています」

と表示されることを確認する。

基本形:

構文例
<アクセス修飾子> interface <インタフェース名>

インタフェースには抽象メソッドを書く。

つまり、

メソッドの内容は書かず、メソッドの形だけを定義する。

例:

構文例
public interface Lockable{
    boolean lock();
    boolean unlock();
}

クラスでインタフェースを実装するときは、

implements

を使用する。

基本形:

構文例
<アクセス修飾子> class <クラス名> implements <インタフェース名>

例:

構文例
class Kinko implements Lockable

クラスの継承とインタフェースの実装を同時に行う場合は、

extends

と

implements

の両方を使用する。

基本形:

構文例
<アクセス修飾子> class <クラス名> extends <継承元クラス名> implements <インタフェース名>

例:

構文例
class LockableJewelBox extends JewelBox implements Lockable
今回のインタフェースの抽象メソッドは、暗黙に public です。クラス側でも public を付けて実装します。

インタフェースを実装したクラスでは、

インタフェースで定義されているメソッドを実装する必要がある。

つまり、インタフェースのメソッドを

オーバライドする。

例えば Lockable に

が定義されているため、Kinko クラスでは次のメソッドを実装する。

コード例
public boolean lock(){
    isLock = true;
    return isLock;
}
コード例
public boolean unlock(){
    isLock = false;
    return isLock;
}

インタフェースの重要な特徴として、

異なる種類のクラスでも、同じインタフェースを実装していれば共通の型として扱える。

という点がある。

今回の場合、

はまったく異なるクラスである。

しかし、どちらも

Lockable

を実装している。

そのため、

Lockable[] l = { k, j };

のように、同じ Lockable 型の配列へ入れることができる。

そして、

for(Lockable i : l) i.lock();

とすることで、配列の各要素に対して共通して

lock()

を呼び出すことができる。

次のように考えることもできる。

Lockable l = k;

Kinko クラスのインスタンス k を、Lockable 型の変数 l に代入する。

その後、

l.lock();

を実行すると、実際には k が持っている lock() が実行される。

次に、

l = j;

とすると、LockableJewelBox クラスのインスタンス j を l に代入できる。

そして、

l.lock();

を実行すると、今度は j が持っている lock() が実行される。

つまり、変数の型を

Lockable

として共通化しておき、

実際に代入されているインスタンスに応じて、それぞれのクラスの lock() を実行できる。

インタフェースの定義

インタフェースの実装

インタフェースを使うメリット

インタフェースを利用すると、

継承関係にない異なるクラスに共通の機能を持たせることができる。

例えば、

Kinko

と

LockableJewelBox

は異なるクラスだが、どちらも

Lockable

を実装しているため、

Lockable

という共通の型として扱うことができる。

これにより、

Lockable[] l = { k, j };

のような配列を作成し、

for(Lockable i : l) i.lock();

のように共通の処理を行うことができる。