📝

【Java】interfaceの概要、メリット、使い方について

2024/01/12に公開

概要

Javaのinterfaceとは、クラスに含まれるメソッドの具体的な処理内容を記述せず、メソッドの型のみを定義したものである。interface側で定義したメソッドは、必ずクラス側で実装しないとコンパイルエラーとなる。そのため、implementsによってinterfaceから派生したクラスでは、メソッドの実装を強制できる、という仕組みである。これだけだと意味不明だと思うので、実際のコードを見せながら、説明する。

また、基本的な記事の内容は、こちらから拝借している。あくまで自分の理解を整理するための記事なので、より詳細な内容を確認したい場合は、下記記事を参考にしてほしい。
https://one-div.com/programming/java/java-interfaces

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が何をするためのものであるか、どういった役割を果たすか、ということが明確になる。これがオブジェクト指向プログラミングの根幹であり、最も大切な部分となる。

参考文献

https://qiita.com/jin007/items/b4dc391a2e83eb999669
https://one-div.com/programming/java/java-interfaces

Discussion