
通常の継承であれば、継承元のクラスにあるメソッドを、子クラスでどんどんオーバーライドしていくことにより、具体化していく形になりました。
継承については、下記の記事でも解説しております。
ただ、普通に継承を使っていくには、いくつか問題点が発生してきます。
結論:継承を使った方が良い場合と使わない方が良い場合
「a is b」というように親と子が全く同じ場合のみ使うのが良いでしょう。(例:雑種は犬ですなど)
「似てるから継承させる」と言う単純な理由では使わない方が良いです。(例:製品Aは製品Bです。など)
継承の際の問題点
オーバーライドをし忘れて、「何も起きない」メソッドだけができてしまう。
親クラスのメソッドに定義されているのに、子クラスでオーバーライドし忘れる問題です。
実際に、そのクラスを使うメイン処理で、親クラスにあるメソッドを呼べるので使おうとしたら、何も起きないということが起こりえそうです。
本当に何もしないメソッドとの区別がつかない。
「何をするのか子クラスで具体化してもらう」メソッドなのか、それとも「本当に何もしないメソッド」なのかの区別がつかないです。
意図せずnewされる可能性がある。
「継承元クラス」は、newされるためのものではありません。基本的には、具体化した子クラスをメイン処理ではnewさせる必要があります。
ただ、そのクラスについて何も知らない開発者であれば、意図せずnewしてしまう可能性があります。
スーパークラスを読まないとサブクラスの内容が理解できない。
いちいちソースが別れていると可読性が落ちてしまいます。継承されているとソースジャンプもしずらいですし。
子クラスでは使わないメソッドなども入りうる可能性がある。
「親クラスでは使ったとしても、子クラスでは必要ない」というケースは往々にして起こりうると思います。開発者にもその判断をさせられることになって強いまいます。親クラスは親クラスの事情でどんどんメソッドは今後増えていくことになりますし、その度に子クラスはその親クラスの必要ないメソッドが自動で追加されてってしまいます。(親クラスは子クラスのことなんて知らないですし)
もし、使わないメソッドなどが親に追加された場合はコードヒントなどで使わないメソッドが増えてしまいますしあまりよい状態ではないです。
ただ、注意点としては子クラスでしか使わないメソッドを追加していくことも「リスコフの置換原則」的にはNGになるので子クラスで独自拡張してしまうのも基本的には避けた方が良いでしょう。(ある特定の場合を除いて)
抽象クラス、抽象メソッドを使う。
上記問題を解決するために「抽象クラス、抽象メソッド」を使いましょう。
構文
1 |
アクセス修飾子 abstract 戻り値 メソッド名(引数); |
「abstract」は、抽象という意味で、詳細未定という意味を示します。
サンプル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public abstract class People { String name; //名前 int weight; //体重 String pc;//パソコン People(String name,int weight){ this.name = name; this.weight = weight; } //自己紹介 abstract void selfIntroduction(); } |
Javaのルールとして、「abstract付」の抽象メソッドを一つでも含むクラスは、abstractをつけた抽象クラスとして宣言する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Japanese extends People { Japanese(String name,int weight){ super(name,weight); } void selfIntroduction() { System.out.println(name + "さんは日本人です。"); System.out.println("体重は、"+ weight + "kgです。"); } } |
結果
親の抽象クラスをnew使用としてもnewできません。
親クラスの抽象メソッドを実装(オーバーライド)しないとエラーになります。
親クラスの抽象メソッドである「selfIntroduction」を子クラスでオーバーライドしていないため、コンパイルエラーになっています。
インターフェイスとは?
「特に抽象度が高い要素を持つクラスの仲間」のこと。(あくまで、クラスと似たようなもので、クラスではないです。)
インターフェイスの特徴
- 全てのメソッドが抽象メソッド
- 基本的にフィールドは一つも持たない。
※通常のフィールドは持たないですが、「public static final」だけは宣言が許されます。また、「public static finalを付いていないフィールド」を用意しても自動的に付きます。
普通の「抽象クラス」と異なり、なぜ特別扱いされるか?(「インターフェイス」という独立した文法名になっているか。)
インターフェイスは、内部実装を一切しないので特別に「多重継承」が許されているため。
(普通のクラスだと、多重継承を許してしまうと、子クラスでどのように実装するか混乱してしまいますからね。)
抽象クラスとの使い分け
違いはほとんどありません。
使い分けとしては、インターフェースは全てのメソッドを抽象化していますが、抽象クラスは一部のメソッドのオーバーライドを強制しないくらいしかないです。なので、全てのメソッドを抽象化できるようであれば、インターフェースを使いましょう。
構文
インターフェース
1 2 3 |
アクセス修飾子 interface インターフェイス名 { … } |
インターフェースを実装するクラス
1 2 3 |
アクセス修飾子 class クラス名 implements インターフェイス名{ … } |
サンプル
インターフェイス
1 2 3 4 5 6 |
public interface People { //自己紹介 abstract void selfIntroduction(); } |
なお、インターフェイスで定義された「抽象メソッド」は、自動的に「public」かつ「abstract」になります。
実装クラス
1 2 3 4 5 6 7 |
public class Japanese implements People { public void selfIntroduction() { System.out.println("Hello World"); } } |
実装クラスでは、「抽象メソッド」をオーバーライドしますが、必ずpublicになります。
実装クラスの命名規則について
インターフェイスを実装したクラスの末尾には、「インターフェイスをimplementsしているクラス」という意味で「Impl」と付けることがたまの慣例になっています。
付けることで、どのインターフェイスを実装しているかわかるという意味合いもあったりします。
ただ、ちゃんとした本格的なプログラムの場合は、インターフェイスから、独立した意味のあるクラス名をつける方が良い場合が多いので、この慣例に従うことはあまり多くありません。
インターフェースと抽象クラスの使い分け
ほとんどありませんが、一部処理を共通化したい場合などには抽象クラスは使えます。子クラスに共通処理があるかどうかを基準にして使い分けるのが良いでしょう。
使い分けとしては、インターフェースは全てのメソッドを抽象化していますが、抽象クラスは一部のメソッドのオーバーライドを強制しないくらいしかないです。なので、全てのメソッドを抽象化できるようであれば、インターフェースを使いましょう。
この記事へのコメントはありません。