親クラスとインターフェース。
どちらもクラスに似た性質を持っているのに、
どうして名前が違って、どう使い分けたらいいのか。
多重継承が可能とか、implementsするかextendsするかの違いとか、
そういうことではなくて、もっと根本の、どうして多重継承可能なのか、
なぜ多重継承が必要になったのか、じゃあ親クラスだけではどう困るのか、
そういったところを知りたかった。
調べても明確に書かれているサイトがなかったので、自分なりに解釈して、結構納得がいったので、まとめてみました。
目次
- 親クラスとは
- 特徴1 「サブクラスの持つ共通の内容はすべて記述」
- 特徴2 「クラスなので、使用前にインスタンス化が必要」
- インターフェースとは
- 特徴1 「記述はメソッド定義のみ、具体的な記述はクラス内で行う。」
- 特徴2 「インスタンス化が不要」
- 親クラスの性質とインターフェースの性質
- 成り立ち(一部想像があります。)
- 例 MapクラスとMapインターフェース
- インターフェースを用いたMap
- 親クラスを用いたMap
- 具体的な使い分け
- まとめ
親クラスとは
親クラスはサブクラスのスーパークラスに当たるクラスです。イメージとしては、サブクラスが複数あり、 それを包含する形で存在しているイメージです。
サブクラスの共通部分に当たるものを、親クラスにまとめておき、
そこから継承という形でサブクラスに持ってくることで、
サブクラスの記述が少なくなり、見た目が綺麗になります。
(プログラミングの中ではこのように、同じ機能を持つ部分を共通化することにより、
「流用」しやすくする=いいコード みたいな認識があります。
なのでいろいろな機能をもったメソッドやクラスがたくさんあり、
初心者を混乱させている面もあります。自分もいまだに混乱します。)
なので、まず
メソッドと、その具体的な内容まで書きます。
たとえば、「買い物をする」というメソッドがあるとき、
親クラスは「ライフにいって、玉ねぎとニンジンを買って、公園の横を曲がって帰ってくる」
といったように親クラスで具体的に記述します。
そして
SuperClass superClass = new SuperClass();
とか書くこれです。(クラスは適当です。)
これが結構今回個人的に大事になるポイントです。
インターフェースとは
対して、インターフェースも似たような特徴を持っています。同じような機能をもっているクラスをまとめてインターフェースで括っているイメージです。
しかし親クラスが「買い物の場所や内容、道順まで記述する」のに対し、
インターフェースはメソッドの定義のみで、その具体的な内容の記述はクラスに委ねています。
「買い物にいく」とだけ書き、その後の道や買うもの等はすべてクラス内で記述します。
まだこの段階では知識が点で存在しています。
次にどうしてこの特徴を持っているかについて説明していきます。
親クラスの性質とインターフェースの性質
同じ機能を持っているクラスを包括している、という面で2つはほとんど同じに思えます。ここがずっと私の疑問でした。なんでほぼ同じなのに2つあるの?
もっというと、
なんで親クラスでは具体的に記述するのに、インターフェースではしないの?
逆に、インターフェースで具体的に記述しないということは、
その方がいいから、ということです。
それはなぜ?
また、インスタンス化が必要なことと不必要なこと、
この点で明確に違いを持たせたくて変えたような気がしました。
たまたま違うのではなく、不便だから必要ないのを作った。という順番です。
特徴を持っているのではなく、特徴を持たせたのかな、と。
成り立ち(一部想像があります。)
結論からいうと、まず親クラスが存在していたようです。それは同じ機能を持つクラスを共通化したい、という意味からでしょう。
しかし、今度はサブクラスの機能に拡張性を持たせたいと思った時、毎回毎回インスタンス化しなくてはならない。
(さらに記述を変えるなら@Override,そして毎回インスタンスの呼び出しが必要となります。)
もともとは共通化したいと考えて作られた親クラスですが、インスタンス化という性質のせいで拡張性が少なくなってしまいました。
インスタンス化する手間さえなければ、似た機能でありながら、拡張性のもった機能ができるのに、、、、。
そして、インターフェースができたのです。
少しわかりにくいので、Mapインターフェースを例に挙げて説明します。
例 MapクラスとMapインターフェース
ここからは完全に想像です。※メソッド名など正確な名前は正確なサイトからご覧ください。
インターフェースを用いたMap
//Mapインターフェース
abstract public interface Map{
//メソッド名のみ記述
get();
~~~
}
set();
}
//HashMap 子クラス
public HashMap implements Map{
//メソッド名と具体的内容を記述
get(){
~~~~~
}
set(){
・・・・
}
}
実に簡潔で拡張性があり、使いやすいです。
メソッド内容の定義を後からすることにより必要最低限の記述を実現しています。
逆に親クラスを使ってMapを表すとどうなるでしょう。
親クラスを用いたMap
///Map親クラス
abstract public class Map<>{
//メソッド名と具体的内容を記述
get(){
~~~
}
set(){
・・・
}
}
//HashMap 子クラス
public subClass extends HashMap{
//この記述が毎度必要
@Override
Map<> Map = new Map();
get(){
~~~~~
}
]
実際には存在しませんが、きっとこうなるんでしょう。
毎度毎度クラスが増えるたびに@Overrideやインスタンス化を行います。
記述を少なくするための親クラスですが、却ってサブクラスの記述が増えました。
こういったように、元は共通化目的で親クラスができた→拡張しづらい→拡張しやすいインターフェースを、という流れだったのではないでしょうか。
具体的な使い分け
ここまで話してきましたが、まとめると共通化したいなら、親クラス
拡張したいなら、インターフェース
ということになります。
むしろさらに簡単にすると、メソッドの中身まで共通するときはクラス、
名前だけが同じ場合(同じような機能を持つことをメソッド名で示す)はインターフェースという覚え方でもいいのかもしれません。
(インターフェースがあることで、getメソッドは取得するんだろうな、setで代入かな、そんな機能があるんだなと大体想像がつきます。)
まとめ
最近はこういう風に、知識を線で覚えるようにしています。最初は忘れっぽい自分の性格をカバーする為理由を付けて覚えていました。
5回Whyを唱えることで本質を見る、というある本の教えに習っています。
しかし
これは本当に結構本質かもしれません。
自分たちが点で覚えている特徴は、
実は後付けというよりもどちらかが先にあって、不便の先に変化させたものを特徴と呼んでいるのかなと。
その過程まで見ると、
先人プログラマーの思考が見えてきます。
実際、自分はもうインターフェースにするか、親クラスにするかもう悩むことはないでしょう。
こういう、こじつけにしても知識を線にする作業、穴埋めの作業は推理小説を読んでいるみたいで少し楽しいです。