【本サイトではGoogleアドセンス、または、アフィリエイト広告を利用しています。】
interfaceとは?
interface(インターフェース)はクラスと似ていますが違います。
クラスはオブジェクトですが、interfaceはオブジェクトではありません。
interfaceは、クラスに機能を追加する為の拡張部品です。
部品なので、単独でインスタンス化して使うことはできません。
例えば、パソコンがあって、そこにカメラやマイクといった周辺機器をつなぐことで、
パソコン単独では実現できなかったビデオ通話の機能が追加されます。
このカメラやマイクこそ、今回のテーマとなるinterfaceです。
interfaceは以下のように定義します。
修飾子 interface インターフェース名 {
// フィールド(定数)
// メソッド(定義のみ)
// defaultメソッド
}
interfaceには、クラスと同様に、フィールドやメソッドを定義できますが、
クラスで定義したものとは以下の点において全く扱いが異なります。
・フィールドは変数ではなく、定数のみ定義できる。(初期化が必要。)
・通常のメソッドには処理が定義できない。(抽象メソッドと同様の扱いになる。)
・defaultメソッドとして、処理を持った静的なメソッドを定義できる。
つまり、interface内では、修飾子を付加せずとも、フィールドには常に「public static final」、
通常のメソッドには「abstract」が付加されている状態と同じ扱いになります。
defaultメソッドについては後述します。
eclipseを使ってinterfaceを新規作成する方法
eclipseを使うと以下の手順でinterfaceを簡単に作成できます。
【手順①】
interfaceを作成したいパッケージを右クリックし、
メニューから「新規 > インターフェース」を選択します。
【手順②】
インターフェースの作成画面が表示されるので、名前を入力し「完了」ボタンを押すと作成完了です!
作成が完了すると、パッケージエクスプローラー上に、
クラスと同様にjavaファイルが追加されますので、確認してください。
interfaceをクラスに実装する
では、作成したinterfaceをクラスとどうやって紐づけるのでしょうか?
とその前に、interfaceをクラスに紐づけることを「実装する」と言います。覚えておきましょう!
interfaceは以下のように実装します!
修飾子 class クラス名 implements インターフェース名1, インターフェース名2, ・・・ {
// インターフェース内に定義したメソッド(オーバーライドして処理を書く)
}
クラス名の後ろに「implements」を付加してinterfaceを指定します。
メソッドは抽象メソッドという扱いになるので、interfaceを実装した
クラス内でオーバーライドして具体的な処理を書く必要があります。
また、大きな特徴の1つとして、カンマで区切って並べることで複数のinterfaceを実装できます。
これを「多重継承」と呼びます。覚えておきましょう!
例えば、ゲームの世界で勇者がいた時、装備できるのは剣だけではありません。
盾や鎧などの防具も装備できます。
そして、勇者は、武器や防具を装備することで、
新しい技を使えるようになったり、ステータスが変わったりします。
また、装備する人によって、使える技が違ったりもします。
現実世界だと、装着することで重いものを運べるようになる
パワードスーツなどがinterfaceとして当てはまります。
interfaceの実装はこのようなイメージを持って使ってみてください。
interfaceと抽象クラス(abstract)の違い
interfaceについてわかってくると、抽象クラスとの違いがわからなくなってきます。
まず、機能的な違いを整理するとこんな感じです。
特徴 | interface | 抽象クラス |
---|---|---|
多重継承 | ○ | × |
インスタンス化 | × | × |
フィールドの定義 | 定数のみ | 〇 |
メソッドの定義 | × | 〇 |
defaultメソッドの定義 | 〇 | × |
抽象メソッドの定義 | 〇 | 〇 |
静的メソッドの定義 | 〇 | 〇 |
違うと言えば違うし、似てると言えば似てます。
これがあなたを混乱させている原因です。
実際のところ、多くのプログラムでは、interfaceと同じようなことを、
抽象クラスを代わりに使うことで実現できてしまいます。
では、それぞれどういう位置づけで、どう使い分けるのか?
この回答として良く言われるのは、「概念を理解する」ことです。
抽象クラスは、オブジェクトの共通定義という概念です。
interfaceは、オブジェクトの機能拡張という概念です。
どうでしょう?ぜんぜん違いますよね?
抽象クラスは、オブジェクト自体を定義するものです。
interfaceは、オブジェクトの持つ機能を定義するものです。
さらに言うと、
抽象クラスは、クラスを作る前に定義するものです。
interfaceは、クラスを作った後に定義するものです。
イメージついてきたでしょうか。
このように、概念を理解しておくことで、それぞれの使い分け方や、使いどころが見えてくると共に、
プログラムもよりシンプルに書けるようになってきます。
abstractについては以下で詳しく説明していますのであわせて読んでみてください!
interfaceを使うメリット
interfaceを使うメリットは、抽象クラスと似ています。
複数のクラスを別々の担当者で協力して作成する時に、
同じ振る舞い(メソッド)なのにそれぞれ別名で定義してしまったり、
こっちは実装しているのに、こっちは実装していないといった
仕様のズレを共通のinterfaceを定義することで防ぐことができます。
また、抽象クラスとは違い、元々の概念がまったく異なるクラス同士でも、
共通の機能を持たせることができます。
例えば、人間とロボットは、生き物と機械で、オブジェクトの派生元としてはまったく別物ですが、
ジャンプできるという振る舞いはどちらも持つことができます。
このように、interfaceは、後付けで機能を追加する時に非常に役に立ちます。
interfaceの使い方(サンプルコード)
それでは、interfaceの使い方をサンプルコードで見ていきましょう!
例として、人間とロボットをオブジェクトとして作成します。
interfaceには、ジャンプと飛行という2つ用意しました。
この2つのinterfaceを実装して人間とロボットを動かしてみます。
まず、サンプルコードの構成は以下とします。
ジャンプ機能のinterfaceです。
ジャンプする時の飛距離を引数として使うので、interfaceのフィールドに高中低の3種類を定義しました。
フィールドは定数になるので、すべてアルファベット大文字で定義しています。
(Javaの命名規約というのがあって、定数は大文字にすることが一般的です。)
package equipment;
// インターフェース:ジャンプ機能
public interface Jump {
// フィールド:飛距離
int DISTANCE_LOW = 1;
int DISTANCE_MEDIUM = 2;
int DISTANCE_HIGH = 3;
// メソッド:ジャンプする
public void jump(int distance);
}
次に、飛行機能のinterfaceです。
「飛行する」というメソッドを用意しました。抽象メソッドの扱いなので処理は実装不要です。
package equipment;
// インターフェース:飛行機能
public interface Flying {
// メソッド:飛行する
public void flying();
}
interfaceができたところで、人間クラスを用意します。
人間は飛行することができないので、ジャンプinterfaceのみを実装します。
抽象メソッドをオーバーライドする為、ジャンプする時の処理もここで実装します。
package human;
import equipment.Jump;
// クラス:人間
public class Human implements Jump {
@Override
public void jump(int distance) {
System.out.println(distance + "メートル、飛んだどー!\n");
}
}
次にロボットクラスです。
ロボットはジャンプも飛行もできるので、interfaceを多重継承しました。
それぞれのもつ抽象メソッドをオーバーライドして処理を実装しています。
package robot;
import equipment.Flying;
import equipment.Jump;
// クラス:ロボット
public class Robot implements Jump, Flying {
@Override
public void jump(int distance) {
System.out.println(distance + "メートル、トビマシタ");
}
@Override
public void flying() {
System.out.println("ヒコウシマス!ギュイィィーーーン!!");
}
}
最後に、mainメソッドで人間とロボットをインスタンス化して動かしてみます。
人間はジャンプ、ロボットはジャンプと飛行をそれぞれ実行します。
package example;
import equipment.Jump;
import human.Human;
import robot.Robot;
// クラス:メイン処理実行用
public class Example {
// メイン処理
public static void main(String[] args) {
// 人間を動かす
Human human = new Human();
human.jump(Jump.DISTANCE_LOW);
// ロボットを動かす
Robot robot = new Robot();
robot.jump(Jump.DISTANCE_HIGH);
robot.flying();
}
}
実行結果は以下です。
1メートル、飛んだどー!
3メートル、トビマシタ
ヒコウシマス!ギュイィィーーーン!!
それぞれinterfaceで実装した機能が使えましたね!
共通のジャンプ機能を、複数のクラスで使えるようにしたり、
飛行機能をロボットだけに実装して使い分けているところがポイントです。
defaultメソッドの使い方
interfaceだけで使える実装方法として、defaultメソッドというものがあります。
defaultメソッドは、interfaceを実装するクラスが使える共通のメソッドです。
interfaceのメソッドは、通常オーバーライドして各クラスの中で具体的な処理の実装が必要になってきますが、
まったく同じ処理を各クラスに分散させて実装させるというルールは、
なるべく共通の処理は1つにまとめましょうというカプセル化の概念を持つJavaの仕様と相反するので、
「default」というワードをメソッドに付加することで、特別に共通の処理として定義できるようにしたものです。
使い方をサンプルコードで見ていきましょう。
先ほどの例に登場した飛行機能のinterfaceに「着陸する」というdefaultメソッドを定義しました。
飛行したら着陸しないとです。わざわざ各クラスに分散する必要もありません。
ということで、着陸した時の動きを共通処理として実装しましょう!
package equipment;
// インターフェース:飛行機能
public interface Flying {
// メソッド:飛行する
public void flying();
// defaultメソッド:着陸する
public default void landing() {
System.out.println("タダイマモドリマシタ!");
}
}
interfaceを実装するロボットクラスです。
defaultメソッドには既に処理が書かれており、
interface内では抽象メソッドの扱いではないので、ロボットクラスではオーバーライド不要です。
package robot;
import equipment.Flying;
// クラス:ロボット
public class Robot implements Flying {
@Override
public void flying() {
System.out.println("ヒコウシマス!ギュイィィーーーン!!");
}
}
main処理でロボットをインスタンス化して飛行→着陸させてみます。
package example;
import robot.Robot;
// クラス:メイン処理実行用
public class Example {
// メイン処理
public static void main(String[] args) {
// ロボット
Robot robot = new Robot();
// 飛行
robot.flying();
// 着陸
robot.landing();
}
}
実行結果は以下です。
ヒコウシマス!ギュイィィーーーン!!
タダイマモドリマシタ!
無事に着陸できました!よかったです!
defaultメソッドは、interfaceを実装したクラスをインスタンス化して初めて
使うことができるという点もポイントになります。
interfaceを変数として使う方法(interface型)
interfaceの使い方の応用編として、interfaceを型とした変数の使い方について説明します。
interfaceを変数として定義するってどういうことでしょう?
それは、以下のサンプルコードで動きを見るとよくわかります。
先ほどの人間とロボットの例を使っています。
書き換えたのはmain処理のみです。
ジャンプ機能のinterfaceを型にして配列で定義しました。
この配列に、人間、ロボットのオブジェクトを順に代入して、
interface型の変数を通してjumpメソッドを実行してみます。
package example;
import equipment.Jump;
import human.Human;
import robot.Robot;
// クラス:メイン処理実行用
public class Example {
// メイン処理
public static void main(String[] args) {
// 人間
Human human = new Human();
// ロボット
Robot robot = new Robot();
// interface型の変数(配列)
Jump jump[] = new Jump[2];
// 人間とロボットを配列にそれぞれ代入
jump[0] = human;
jump[1] = robot;
// 配列の順番にジャンプ
for (int i = 0; i < jump.length; i++) {
jump[i].jump(Jump.DISTANCE_LOW);
}
}
}
実行結果は以下です。
1メートル、飛んだどー!
1メートル、トビマシタ
同じ配列の変数から同じメソッドを呼び出しているのに、
ループ1回目と2回目で振る舞いが変わりました!
このように、interface型の変数に、そのinterfaceを実装しているオブジェクトを
代入することで、オブジェクトが個々に定義しているメソッドを実行してくれるのです!
配列の1つ目には人間クラス、2つ目にはロボットクラスのオブジェクトが入っているので、
interface型の変数を通して間接的に各クラスのメソッドを呼び出していると考えればわかりやすいです。
でも、これが何の役に立つんでしょう?
例えば、人間1、ロボット1、ロボット2、人間2、ロボット3、人間3、人間4・・・
といったように複数のオブジェクトが並んでいて、
順にジャンプさせる時のプログラムを素直に書くとどうなるでしょう?
// 人間
Human human1 = new Human();
Human human2 = new Human();
Human human3 = new Human();
Human human4 = new Human();
// ロボット
Robot robot1 = new Robot();
Robot robot2 = new Robot();
Robot robot3 = new Robot();
human1.jump(Jump.DISTANCE_LOW);
robot1.jump(Jump.DISTANCE_LOW);
robot2.jump(Jump.DISTANCE_LOW);
human2.jump(Jump.DISTANCE_LOW);
robot3.jump(Jump.DISTANCE_LOW);
human3.jump(Jump.DISTANCE_LOW);
human4.jump(Jump.DISTANCE_LOW);
・・・
こんなこと書いてられないですよね?
そこで役に立つのがinterface型の変数です。
先ほどの例を使ってfor文でループすれば、まったく同じプログラムを1行書くだけで、
配列に入っているオブジェクトの特性にあわせたメソッドを切り替えて実行できるわけです。
プログラムをシンプル、かつ、読みやすくすることで、品質も上がります!
interfaceの使い方 まとめ
Javaのinterfaceについて以下説明しました。
・interfaceについて
・interfaceをクラスに実装する(implements)
・interfaceと抽象クラス(abstract)の違い
・interfaceの使い方
・defaultメソッドについて
・interfaceを変数として使う方法
プログラムを書く時にinterfaceを上手に使うのは非常に難しいです。
しかし、こうやってイメージをしっかり理解しておくことで、
プログラムを書いている時にふと使いどころが見えてきたりします。
使い方を誤るとトリッキーなコードになってしまいますが、
上手に使えれば、シンプルで素晴らしいコードが出来上がります。
interfaceを使うことによるメリットは大きいので、
苦手意識を持たず、ここでしっかり理解しておきましょう!