学習目標
- Javaにおけるインタフェースの概念を理解する
- インタフェースを定義できるようになる
- インタフェースを実装したクラスを作成できるようになる
目次
- 10.1 Javaにおけるインタフェース
- 10.2 インタフェースの定義と実装
このスライドでは扱わない内容
授業内課題では扱うため、参考文献等で確認すること。
- インタフェースのフィールド
- 参考文献 P.117
目を通しておく内容
- DebugPrintable
- 参考文献 P.111~
- スーパーインタフェース
- 参考文献 P.119
参考文献:
Java言語プログラミングレッスン 第3版(下) オブジェクト指向を始めよう
オブジェクト指向の基本的な考え方
オブジェクト指向では、オブジェクト同士はメッセージの送受信によってやり取りを行う。
Javaでは、実際には一方のクラスから別のクラスのメソッドを呼び出し、必要に応じて戻り値を受け取る。
つまり、オブジェクトやクラス同士の
「境界面・接点」
にあたるものがメソッドであると考えることができる。
この「境界面・接点」を インタフェース と考える。
クラスの継承とメソッド
例えば、次のクラスがあるとする。
Kinkoクラス
lock()メソッドunlock()メソッド
TaikaKinkoクラス
lock()メソッドunlock()メソッド
TaikaKinko クラスが Kinko クラスを継承している場合、
Kinkoのlock()TaikaKinkoのlock()
を共通のメソッドとして扱うことができる。
同様に、
Kinkoのunlock()TaikaKinkoのunlock()
も共通のメソッドとして扱うことができる。
継承関係にないクラスの場合
ここで、次の Car クラスを考える。
Carクラス
lock()メソッドunlock()メソッド
Car にも Kinko と同じ名前の
lock()unlock()
が存在する。
しかし、
Car と Kinko の間には継承関係がない。
そのため、名前が同じメソッドであっても、共通のメソッドとして扱うことができない。
無理に継承させる場合の問題
例えば Box クラスを作り、
- Kinko
- Car
の両方に Box を継承させれば、共通のメソッドとして扱えるようにすることはできる。
Boxクラス
lock()unlock()
しかし、このクラス構造には問題がある。
例えば、
CarはBoxではない- すべての箱に鍵が必要とは限らない
というように、不自然な継承関係になってしまう。
インタフェースを利用する
そこで、クラスを継承させるのではなく、
インタフェース
を定義する。
例えば、
Lockableインタフェース
lock()unlock()
という2つのメソッドを持つインタフェースを定義する。
そして、
- Kinko
- Car
などのクラスで Lockable インタフェースを実装する。
これによって、継承関係にないクラスであっても、
lock()` と `unlock()
を共通のメソッドとして扱うことができる。
なお、TaikaKinko は Kinko を継承しているため、Kinko が Lockable を実装していれば、TaikaKinko で改めてインタフェースを実装する必要はない。
インタフェースとは
継承関係にないなどの無関係なクラスであっても、インタフェースを使用することで共通のメソッドを持たせることができる。
インタフェースでは、
共通のメソッドの形
を定義する。
そのため、基本的にはメソッドの中で実行する具体的な処理は記述せず、メソッドの形のみを宣言する。
また、インタフェースには次の特徴がある。
- 共通のメソッドを定義する
- メソッドの形のみを定義する
- メソッドの具体的な処理は記述しない
- コンストラクタを持たない
- インスタンスを作成できない
- 通常のフィールド変数は持てない
- 定数の宣言は可能
インタフェースの定義
インタフェースは interface を使用して定義する。
例:
public interface Lockable{
boolean lock();
boolean unlock();
}クラスと同様に、ファイル名は
Lockable.java
とする。
抽象メソッド
インタフェース内では、
boolean lock();
のように、メソッドの具体的な処理を書かず、
メソッドの形だけを宣言する。
このようなメソッドを
抽象メソッド
という。
実装:Kinko と LockableJewelBox → Lockable
矢印は、継承では親から子、実装ではクラスからインタフェースを表します。
今回の例では、次の構造を使用する。
Kinkoクラス
printDeposit()- 預金額を表示
lock()unlock()
Lockable インタフェースを実装する。
Boxクラス
printEmpty()- 箱の中が空かどうかを表示
JewelBoxクラス
Box クラスを継承する。
LockableJewelBoxクラス
JewelBox クラスを継承する。
さらに、
Lockable
インタフェースを実装する。
持つメソッド:
printEmpty()lock()unlock()
Lockableインタフェース
lock()unlock()
1. 共通のメソッドを定義する
以下の6ファイルは、すべて C:\local-doc\github\pro3\10-a-1 に保存してください。フォルダがなければ作成し、各コード欄のファイル名を使用します。
public interface Lockable{
boolean lock();
boolean unlock();
}2. 金庫にロック機能を実装する
Kinko クラスで Lockable インタフェースを実装する。
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 インタフェースに定義されている
lock()unlock()
を Kinko クラスの中で実装する。
3. 箱と宝石箱を作る
class Box{
boolean isEmpty = true;
public Box(boolean e){
isEmpty = e;
}
public void printEmpty(){
if(isEmpty == true)
System.out.println("中身は空です.");
else
System.out.println("中に何か入っています.");
}
}class JewelBox extends Box{
public JewelBox(boolean e){
super(e);
}
}JewelBox は Box を継承するため、
extends
を使用する。
4. 宝石箱にロック機能を追加する
LockableJewelBox は、
JewelBoxクラスを継承するLockableインタフェースを実装する
という2つの関係を持つ。
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();
}
}ここでは、
lock()を実装unlock()を実装printEmpty()をオーバライド
している。
printEmpty() では isLock の状態によって表示内容を変更する。
ロックされていない場合は、
super.printEmpty();
によって親クラスの printEmpty() を呼び出す。
5. 動作を確認するプログラムを作る
各クラスの動作確認を行う。
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. コンパイルと実行
C:\local-doc\github\pro3\10-a-1 に移動してください。cd /d C:\local-doc\github\pro3\10-a-1それぞれのファイルをコンパイルする。
javac Lockable.javajavac Kinko.javajavac Box.javajavac JewelBox.javajavac LockableJewelBox.javajavac 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インタフェースを実装したクラスでは、
インタフェースで定義されているメソッドを実装する必要がある。
つまり、インタフェースのメソッドを
オーバライドする。
例えば Lockable に
lock()unlock()
が定義されているため、Kinko クラスでは次のメソッドを実装する。
public boolean lock(){
isLock = true;
return isLock;
}public boolean unlock(){
isLock = false;
return isLock;
}インタフェースの重要な特徴として、
異なる種類のクラスでも、同じインタフェースを実装していれば共通の型として扱える。
という点がある。
今回の場合、
KinkoLockableJewelBox
はまったく異なるクラスである。
しかし、どちらも
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() を実行できる。
インタフェースの定義
interfaceを使って宣言する- インタフェースのメソッドは抽象メソッド
- メソッドの形を定義する
- 通常のフィールド変数は持たない
- 定数フィールドは定義可能
インタフェースの実装
- クラスでインタフェースを実装するときは
implementsを使用する - 実装するインタフェース名を指定する
- インタフェースに定義されたメソッドをクラス側でオーバライドする
インタフェースを使うメリット
インタフェースを利用すると、
継承関係にない異なるクラスに共通の機能を持たせることができる。
例えば、
Kinko
と
LockableJewelBox
は異なるクラスだが、どちらも
Lockable
を実装しているため、
Lockable
という共通の型として扱うことができる。
これにより、
Lockable[] l = { k, j };
のような配列を作成し、
for(Lockable i : l) i.lock();
のように共通の処理を行うことができる。