【Java】interfaceの概要、メリット、使い方について
概要
Javaのinterfaceとは、クラスに含まれるメソッドの具体的な処理内容を記述せず、メソッドの型のみを定義したものである。interface側で定義したメソッドは、必ずクラス側で実装しないとコンパイルエラーとなる。そのため、implementsによってinterfaceから派生したクラスでは、メソッドの実装を強制できる、という仕組みである。これだけだと意味不明だと思うので、実際のコードを見せながら、説明する。
また、基本的な記事の内容は、こちらから拝借している。あくまで自分の理解を整理するための記事なので、より詳細な内容を確認したい場合は、下記記事を参考にしてほしい。
interfaceの使い方
基本編
interfaceおよびimplementsによって「interfaceから派生したclass」は、それぞれ以下のように定義する。まず、こちらで大枠を掴んで欲しい。実践編では、実際のコードを交えて、より細かく説明する。
interface
interface インターフェース名 {
メソッドの定義;
}
class
class クラス名 implements インターフェース名 {
インターフェースで定義したメソッドの実装;
}
実践編
概要で説明したとおり、interfaceにはメソッドの具体的な処理内容は記載しておらず、メソッドの定義をしているだけである。
interface
// 動物を表現する「Animal」インターフェースを定義する
interface Animal {
// 動物が鳴くメソッドを定義する
void sound();
}
そして、CatやDogというclassを見ればわかるが、Animalをimplementsすることで、interfaceで定義したsound()を継承している。ちなみに、このsound()をCatやDogで実装しないと、コンパイルエラーとなる。これが、「implementsによってinterfaceから派生したクラスでは、メソッドの実装を強制できる」という意味である。
class
// 猫クラスを実装する
class Cat implements Animal {
public void sound() {
System.out.println(“にゃ~!”);
}
}
// 犬クラスを実装する
class Dog implements Animal {
public void sound() {
System.out.println(“ワンワン!”);
}
}
最後に、MainメソッドでCatとDogをインスタンス化させて、実行できる。
Main
// Mainメソッド
public class Test {
public static void main(String[] args) {
// 3つの動物クラスをそれぞれインスタンス化する
Cat cat = new Cat();
Dog dog = new Dog();
// 各クラスの鳴き声メソッドを実行する
cat.sound();
dog.sound();
}
}
interfaceのメリット
ポリフォーリズム
ポリフォーリズムとは、「多様性」を意味し複数のオブジェクトが「同じメソッドでそれぞれ異なる結果」を出すことを意味する。上記の例で言えば、sound()という鳴き声メソッドは一緒だが、その出力結果はCatやDogクラスで異なる。
他にも、乗り物を例に挙げると、移動方法という関数が実装されるだろう。その時、飛行機であれば出力結果は「飛ぶ」になるし、電車で言えば「走る」になる。
sound()や移動方法()といった形でメソッドの入り口を定義して、実際の処理はそれぞれのクラスで行うことができるので、「とりあえず定義だけして、実装は後回しにする」ということが可能となる。とりあえず大枠だけ決めといて、細かい仕様は実際に検証しながら搭載できる。
多重継承
多重継承とは、クラスで複数のinterfaceを継承することである。今回はclass Dog implements Animalといった形で継承したが、class Dog implements Animal, Character, etc.. といった形で、いろんなinterfaceを継承する、という意味である。こちらについては、実際のコードを見せながら、解説する。
基本編
ゲームでキャラクターのクラスを設計する場合を考える。このとき、キャラクターは単なる村人(モブキャラ)、兵士、プレイヤーなど多岐に渡るだろう。ただモブキャラはその場にいればいいだけだが、兵士は武器や戦闘機能が必要だし、プレイヤーは操作やレベルアップ等の必要機能が増える。interfaceでも機能を小分けにして組み合わせることで、不要機能がない効率的なクラス設計ができる。
実践編
まずは必要となるinterfaceを実装する。必要となる要素は以下のとおり。
- ダメージ処理を行うCharacter
- 武器に関するWeapon
- 移動処理を行うControl
// キャラクターを形作る「Character」インターフェースを定義する
interface Character{
// ダメージ処理を行うメソッドを定義する
void damage();
}
// 武器に関する「Weapon」インターフェースを定義する
interface Weapon{
// 武器で攻撃する
void attack();
}
// 入力に応じた処理を行う「Control」インターフェースを定義する
interface Control{
// 移動するメソッドを定義する
void move();
}
では、実際にそれぞれのキャラクタでinterfaceを多重継承する。まずはモブキャラ。こちらは、基本要素のみで良いので、多重継承はせず、characterのみ実装する。
// モブキャラ用クラス(基本要素のみでOK)
class Mob implements Character{
public void damage() {
System.out.println(“モブキャラがダメージを受けた!”);
}
}
次に兵士用クラス。こちらはcharacterに加えて、攻撃の要素も必要なので、Weaponを実装する。
class Soldier implements Character, Weapon{
public void damage() {
System.out.println(“兵士がダメージを受けた!”);
}
public void attack() {
System.out.println(“兵士が武器で攻撃した!”);
}
}
最後にプレイヤー。こちらはCharacterとWeaponに加えて、自分自身が移動するときの移動要素も必要。
// プレイヤー用クラス(コントロールの要素も必要だがAIは不要)
class Player implements Character, Weapon, Control{
public void damage() {
System.out.println(“プレイヤーがダメージを受けた!”);
}
public void attack() {
System.out.println(“プレイヤーが武器で攻撃した!”);
}
public void move() {
System.out.println(“プレイヤーの移動中…”);
}
}
このように、上記のプログラムでは、全キャラクターに共通の機能に加えて、武器や操作といった、キャラクター別の機能もある。これらを必要に応じて、classごとに多重継承することができる。
ちなみに、多重継承できるか否か、というのがabstractとの大きな違いである。抽象クラスは、一つのクラスからしか派生できない。
重要メソッドの実装漏れ
Javaのinterfaceを活用することで、重要なメソッドの実装漏れを防げる。前述したとおり、interfaceを継承したクラスでは、メソッドの実装が強制される。実装されていなければ、コンパイルエラーが発生して実行できない。例えば、先ほどのゲームの例で言えば、Controlという移動メソッドをプレイヤークラスで継承しているにもかかわらず、実装されていなければエラーが発生する。
大規模プロジェクトになったとき、設計変更や機能追加で実装漏れを防ぐことができるため、そういったトラブルを事前に防止できる。
interfaceの継承方法
基本編
通常のクラスと同様に、interfaceを継承することもできる。継承すると、継承先のクラスでは、継承元のすべての機能を含む。継承する際は、通常のクラスと同じようにextendsを使用する。
実践編
では実際に「乗り物」のサンプルプログラムで、継承方法を確認する。
まずこちらで、Vehicle interfaceを定義する。また、accel()やbrake()といったメソッドを定義する。
// 乗り物の「Vehicle」インターフェースを定義する
interface Vehicle {
// アクセル
void accel();
// ブレーキ
void brake();
}
Car interfaceでVehicle interfaceを継承する。その上で、handle()という新しいメソッドを追加する。
// クルマの「Car」インターフェースを、「Vehicle」を継承して定義する
interface Car extends Vehicle {
// ハンドル
void handle();
}
Truck interfaceでCar interfaceを継承する。その上で、load()という新しいメソッドを追加する。
// トラックの「Truck」インターフェースを、「Vehicle」を継承して定義する
interface Truck extends Car {
// 貨物の積み込み
void load(Object cargo);
}
DeliveryCarクラスでTruck interfaceを継承する。その上で、今まで継承してきたaccel, brake, handle, loadといったメソッドを実装する。
// 宅配トラック「DeliveryCar」クラスを実装する
class DeliveryCar implements Truck {
public void accel() {
System.out.println(“アクセル操作!”);
}
public void brake() {
System.out.println(“ブレーキ操作!”);
}
public void handle() {
System.out.println(“ハンドル操作!”);
}
public void load(Object cargo) {
System.out.println(“貨物” + cargo + “を搭載!”);
}
}
ここまでの流れを整理すると、最初にすべての乗り物に共有したvehicleを定義している。そして、最初のinterfaceであるcarはvehicleを継承し、truckはそのcarを継承する。最後に、delivery carはtruckを実装して、結果的に4つのメソッドが搭載される形となる。
このように継承を活用することで、それぞれのinterfaceが何をするためのものであるか、どういった役割を果たすか、ということが明確になる。これがオブジェクト指向プログラミングの根幹であり、最も大切な部分となる。
参考文献
Discussion