概要
インターフェースとは、内部実装を知る必要なく、利用する機能の接点のみを提供する仕組みです。
接点をソフトウェア的にいうと、メソッドのシグネチャと戻り値の型に当たります。
つまり、どんなメソッドにこういう値を渡せばこの型でデータが返ってくるよというのが定義されたものがインターフェースです。
基本構成
変数、メソッドを持つことができます。
変数
public かつ static かつ final(外部から参照可能かつ変更不可)なものだけを定義できます。
メソッド
アクセス修飾子はpublic または privateが指定可能です。
省略した場合、暗黙的にpublicとして扱われます。
privateメソッドは具象メソッドとして利用します。(Java9から利用可能になりました)
これはインターフェースの内部実装として利用するためで、実装先では扱えないものです。
abstractはつけなくても暗黙的に抽象メソッドとして扱われるので省略可能です。
default と staticは具象メソッドとして扱います。
(defaultはJava8から利用可能になりました)
コード例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public interface ISample { /** * 変数 */ public static final int NUM = 100; /** * メソッド */ // abstractは省略可能 // アクセス修飾子を省略すると暗黙的にpublicとして扱う public int num(); abstract int otherNum(); // privateは具象メソッドのみ定義可能 private int privateNum() { return 0; } // デフォルトメソッドは具象メソッドを定義するために使う public default void printNum() { System.out.println("privateNum = " + privateNum()); } // staticなメソッドはpublic/privateで指定可能 private static int privateNum2() { return 1; } public static void staticMethod() { System.out.println("static method"); System.out.println("NUM = " + NUM); System.out.println("privateNum2 = " + privateNum2()); }; } |
defaultメソッド
前述のとおりですが、Java8から利用可能になった修飾子で、具象メソッドを実装するために指定します。
多重継承
インターフェースは複数の実装(implements)を持たせることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public interface ISample { // 内容は記載済みのため割愛 } // 別のインターフェース public interface IOthreSample { public void otherPrint(); } // 複数のインターフェースを実装(implements) public class Sample implements ISample, IOthreSample { @Override public int num() { return 0; } @Override public int otherNum() { return 0; } @Override public void otherPrint() { } } |
オーバーライド
インターフェースは基本的にメソッド名と戻り値のみを提供するだけなので、実装先のクラスで具象メソッドを実装する必要があります。
その場合、@Overrideアノテーションをつけてオーバーライドを明示します。
また、オーバーライドしないとコンパイルエラーになるため、実装の強制による実装漏れを防げるというメリットがあります。
ポリモーフィズム
インターフェースの機能を最大限に活用するにはポリモーフィズムの理解が不可欠です。
ポリモーフィズムとは、インスタンスに基づいた振る舞いをさせる仕組みです。
もしポリモーフィズムを使わなければ、if文をたくさん書くことになります。
ホテルの料金計算を例に簡単なコードで見ていきたいと思います。
インターフェースと各金額クラス
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// 料金計算インターフェース public interface ICalcAmount { public int amount(); public default void print() { System.out.println(amount()); } } // 大人料金クラス public class Adult implements ICalcAmount { @Override public int amount() { return 11000; } } // 子ども料金クラス public class Child implements ICalcAmount { @Override public int amount() { return 5500; } } // シルバー料金クラス public class Silver implements ICalcAmount { @Override public int amount() { return 7800; } } |
Mainメソッド
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class IMain { public static void main(String[] args) { // ポリモーフィズムにより、インスタンスで動作が決まる ICalcAmount adult = new Adult(); ICalcAmount silver = new Silver(); ICalcAmount child = new Child(); adult.print(); // 11000 silver.print(); // 7800 child.print(); // 5500 // if文でロジックを分ける int age = 20; if(age < 20) { System.out.println(5500); } if(age >= 20 && age < 65) { System.out.println(11000); } if(age >= 65) { System.out.println(7800); } // 11000 } } |
単純な例なのでメリットがわからないかもしれません。
そこで、次のような想定を検討してみます。
・新たな料金体系に変更することなったら?
・子供料金だけ、キャンペーンを適用することになったら?
・料金判定が色んな箇所でコーディングされており、すべての箇所を修正することになったら?
もしif文で書いていたら、すべての箇所の修正とテストを行わなければなりません。
でもポリモーフィズムを活用していたら、特定のクラスまたはインターフェースの修正で済みます。
場合によってはif文と変わらないテスト工数がかかるかもしれませんが、コードの修正コストは格段に少なく、また安全です。
他の例を参照したい場合、以下もご確認下さい。
⇒映画のレイティング判定をStateパターンで実装する
まとめ
- インターフェースにはpublic かつ static かつ finalな変数を持つことができる。
- メソッドは暗黙的に、publicかつabstractとして扱われる。
- defaultメソッドは具象メソッドとして実装する。
- インターフェースは多重継承が可能である。
- 実装先クラスに抽象メソッドの実装を強制させることで、実装漏れを防ぐことができる。
- ポリモーフィズムによって、保守しやすいコードを書くことができる。