僕が勉強し始めたときに勝手に定義した3大なんであるのこの構文のひとつ、Interfaceさん。
この人がどういうときに使われるの?
果たして本当に必要な構文なの?
というところを紐解いて行こうと思います。
ちなみに、どうてもいいですが残りの3大なんであるのこの構文はextendsとabstractです。若気の至り。
Interfaceってなんぞや
Interface(いんたーふぇいす)というのは接触面、中間面を表す英単語なんだそう。(Wikipediaより)
パソコンについてるUSBなんかもパソコンと、色々な周辺機器をつなぐためのインターフェイスなんて呼ばれますよね。
これは色々な周辺機器がそれぞれ独自のコネクタだったら接続するときにめんどくさすぎるので、USBという規格に従って共通化されているからなのですが。
そんな感じで、他のクラスと共通の入出力などをInterfaceを実装することで実現することができます。やっていこう。
Interfaceのつくりかた
JavaでInterfaceを定義するにはinterface
キーワードを使います。
Interfaceにはメンバ変数と抽象メソッドのみを定義することができます。
メンバ変数には自動的にpublic static final
の扱いになるので、定数としてのみ使うことができます。
メソッドは抽象メソッドになるので、Interfaceに処理を定義することはできず、メソッドの名前・返り値・仮引数のみ定義できます。
interface ExampleInterface{
int getNum();
void setNum(int num);
}
で、これを使うクラス側でimplements
キーワードを使って実装してあげます。
implements
は「実装」という意味なんですって。
class ExampleClass implements ExampleInterface{
int num = 10;
@Override
int getNum(){
return num;
}
@Override
void setNum(int num){
this.num = num;
}
}
前述したことの繰り返しになりますが、Interfaceに定義したメソッドは勝手に抽象メソッドになるので、必ずオーバーライドする必要があります。
こんな感じでとりあえず、今までメッチャメチャ書いてきたgetterとsetterをInterfaceにして実装してみました。
Interfaceの便利な使い方
キャストできる
継承したときのようにInterfaceもキャストができます。
とりあえず、適当なクラスを作りました。
Interfaceはさっきのを使います。
interface ExampleInterface{
int getNum();
void setNum(int num);
}
適当なクラスAとBを作ります。
class ClassA implements ExampleInterface{
int num = 10;
@Override
int getNum(){
return num;
}
@Override
void setNum(int num){
this.num = num;
}
}
class ClassB implements ExampleInterface{
int num = 20;
@Override
int getNum(){
return num;
}
@Override
void setNum(int num){
this.num = num;
}
}
じゃあ、こいつ動かしてみますか。
Interfaceの長所を使わずに適当に出力してみます。
class RunClass{
public static void main(String[] args){
ClassA classA = new ClassA();
ClassB classB = new ClassB();
printNum(classA);
printNum(classB);
}
static void printNum(ClassA classA){
System.out.println(classA.getNum());
}
static void printNum(ClassB classB){
System.out.println(classB.getNum());
}
}
説明をわかってもらうためにだいぶめんどくさい書き方しました。
今、定義したクラスが2つしかないからいいけど、もし他にもClassCとかClassDとか増えて来たら大変ですよね。
じゃあ、Interfaceの型にキャストして処理してみましょう!
class RunClass{
public static void main(String[] args){
ClassA classA = new ClassA();
ClassB classB = new ClassB();
printNum(classA);
printNum(classB);
}
static void printNum(ExampleInterface instance){
System.out.println(instance.getNum());
}
}
こんな感じでExampleInterface
を実装したクラスはすべてprintNum
メソッドで処理できるようになりました。
このように実装したすべてのクラスで共通の名前のメソッドがあることを約束したいときに利用できるんじゃないかなと思います。
複数実装ができる
extends
キーワードによる継承は2つも3つもクラスを継承することはできませんでしたが、Interfaceはできます。
パソコンにUSBだけじゃなくてイヤホンを挿すアナログ端子とか、LANポートがあったりしますよね。
そんな感じで、Interfaceをガシガシ追加することで、様々な入出力に対応させることができます。
なので、Interfaceはあくまでも入出力関連のメソッドを実装するのに利用し、ゴリッゴリの処理を書くのに使うべきではないと思います。
ひとつのクラスが機能を持ちすぎていると、1つのファイルに1000行を超えたりとかしてやべぇことになってしまい、どこに何を書いたかわからなくなるのでやるべきではありません。
なので、ゴリッゴリの処理を実装したいときは抽象クラスにして、また他のクラスを継承したくなったらそもそも機能を切り分けて別のクラスを作ることを考えたほうがいいでしょう。
お前は○○する係な!と、ひとことで言い表せるくらいがベストなんじゃないかな...?
シメ
Interfaceと抽象クラスの使い分けは誰もが一度は直面する問題みたいなので、自分の中での最適解が見つかるまではGoogle先生と仲良くなって、Qiitaのエントリとか、なんちゃらエンジニア塾の記事を読んでみたらいいんじゃないかなって思います。
以上!