Javaでは、複数の実装を可能にするデータ型を作成する方法が二つあります。
抽象型クラスとインタフェースです。
この二つの違いは、抽象クラスは、中身を含めることができますが
インターフェイスはできないという点でしたが
Javaの1.8からdefaultを活用して、オントフェイスもbodyを持つようになりました!
public interface Test {
public void existingMethod();
default public void newDefaultMethod() {
System.out.println("New default method is added in interface");
}
}
抽象型クラスは、実装するには、継承(継承)の使用が必修です。
Javaでは、マルチ継承ができないので、抽象クラスで柔軟なコードを作る場合
色んな制限が続きます。
しかし、インターフェースはクラス階層(hierarchy)に関係なく
複数の実装が可能です。
また、既存のクラスをインターフェイスの実装に追加することも簡単です。
インターフェースを作成した後、クラスのメソッドを確認して追加し、
implementsだけつけてくれたら実装できますます。
しかし、継承を必要な抽象型クラスには制限が付きます
以下は、簡単に作ってみた抽象クラスと階層グラフです
abstract public class A {
public abstract void aa();
}
abstract public class B extends A {
public abstract void b();
}
public class C extends B{
@Override
public void aa() {}
@Override
public void b() {}
}
Bは抽象クラスであるAを継承するが、
本人が抽象クラスなので実装する必要がありません。
代わり最後に継承するCにその役割を渡します。
Cは両方を実装しなくてはなりません。
このように抽象クラスは、クラス階層の中で密接な関係を結んでいます。
ので、この中間に抽象クラスを追加して実装をしようとすることは不可能です
インターフェースは、ミックス・イン(minxin)を定義するのに理想的です。
ミックスインが何でしょう?
クラスが「主型」に加えて追加で実装することができる型で、オプション機能を宣言するために使われる。
public class C implements D, F {}
簡単に説明すると、D,Fを混ぜ(mix)入れ(in)だと見てMixinと呼ばれるようです。
インターフェースの利点に戻ってみります
public interface Singer {
AudioClip sing(Song s);
}
public interface Songwriter {
Song compose(boolean hit);
}
歌手と作曲家のクラスがあります。
しかし、歌手の中で作曲家を兼ねる方もいます。
public interface SingerSongwriter extends Songwriter, Singer{
AudioClip strum();
void actSensitive();
}
そんな時のインターフェイスをextendsした形で作ってくれればされます。
このようにインタフェースは、階層に関係なく、非常に柔軟な機能を提供しています
もし、このような型を実装したいインタフェースがない場合を仮定してみてください。
SingerSongwriterというクラスを実装して
その中にsongwriterとsingerを入れておかなければならないです。
3つだからこう終わりますが、もし百ぐらいならどうでしょう・・。
組み合わせられるクラスが数千は軽く超えるはずです。
このような現象を「combinatiorial explosion」と呼びます。
このような現象を軽く解決してくれるインターフェイスは、
非常に便利なツールでしょう。
元々インターフェースの中でメソッドのBodyを作ることができないが、
プログラマが使うコードを提供する方法がまくったくないわけではありません。
抽象骨格実装(abstract skeletal implementation)クラスを重要なインターフェイスごとにおけば、インターフェースの長所と抽象クラスの利点を生かすことができます。
下はサンプールコードです。
public interface A {
void aa();
void bb();
}
public abstract class B implements A {
@Override
public void aa(){
System.out.println("aa");
}
}
抽象クラスなので、実装をしなくても大丈夫ですが
overrideは可能です。簡単にaaを出力してみます。
public class C extends B{
@Override
public void bb() {
this.aa();
}
}
今回は抽象クラスBを継承するCを作ってみました。
B抽象クラスは、bbをオーバーライドしていないので、
bbのオーバーライドが強制されます。aaを呼び出してみます。
public class D {
public static void main(String[] args){
C c = new C();
c.aa();
}
}
そしてCのオブジェクトを生成して
aaを呼び出してみます
予想通りaaが出ます。
このコードでが言いたいことは
Cのaaはボディがある抽象クラス、bbはインターフェースのコードを使っています。
Cがインターフェイスを実装する効果を得ていますが
具体化されたボディがあるメソッドをそのまま使うことができます。
このように骨格がある実装クラスを「AbstractInterface」と呼んでいます。
最新のJAVAではinterfaceでdefaultを使うことができますので、
ボディがある柔軟な実装をしたい場合、
あえてこの抽象骨格実装を使う必要はありません。
方法の一つとして知っておきましょう。
ちなみにJavaのAPIのうち、
これらの抽象骨格実装方法で作られたクラスもあります。
インターフェースの唯一の欠点を挙げてみたら
抽象クラスがインタフェースより発展させやすい性格という点です。
抽象クラスの実装は、階層(hierarchy)に入るので
親クラスでメソッドを新たに追加した場合
下の全てのクラスは、そのメソッドを使用することができます。
インターフェイスは、インタフェースに新しいメソッドを追加したら、
下から型が割れ、新しいメソッドを実装するようにコンパイルエラーが出ます。
しかし、defaultの登場でこれも昔話になりました。
サンプールコードを作ってみました。
public interface A {
void aa();
void bb();
}
public class B implements A{
@Override
public void aa() {
}
@Override
public void bb() {
}
}
Aを実装するBを作れば、二つのメソッドをオーバーライドするようにエラーが出ます。装をしました
ここで、Aにメソッドを追加してみます。
ccを追加みました。
当然、Bからコンパイルエラーが出ます。
Aに戻って
defaultを作成し、ボディをつけました。
先まであった、エラーが消えました。
Bで実装していないままでも
新しいクラスを作成し、Aインターフェイスで定義した
内容をそのまま使うことができます。
このようにpublicインタフェースを実装する既存クラスを壊さずに、
新しいメソッドをインターフェイスに追加する方法はないとの話も
昔話になってしまいました。
なぜdefaultを作ったのか吾人的に少し考えてみたところ、
「抽象骨格クラスを使用しても結局、
単一継承のみを許可するJavaでは抽象骨格クラスも柔軟的ではない方法」
と思ったのではないでしょうか。私の予想です。
最後にまとめると
「Javaでabstractはなるべく使わない方がいいです。」になります。
댓글 없음:
댓글 쓰기