ポリモーフィズムとは何か?型とオブジェクトの関係を紐解く
Javaを勉強していると、ポリモーフィズムという概念に出会いました。自分は具体的な使い方を知る前に、原理的なコンセプトをおさえたい性格なので、自分なりに解釈した結果を記します。
私が見聞したポリモーフィズムのよくある説明
ポリモーフィズムの原義は「多様性」を意味しますが、本記事の文脈では、変数の型がオブジェクトの型と異なっていても、変数とオブジェクトの間に、クラスの親子関係やインターフェースの実装があればエラーを起こさず動作することを指しています。
通常の型の一致
通常、変数の型とオブジェクトの型は一致させます。
Animal animal = new Animal();
しかし、オブジェクトがクラスを継承していると、変数をスーパークラスの型とみなすことができます。
Animal animal = new Dog(); // Dog はAnimalを継承
また、インターフェースを実装している場合は、インターフェース型として該当オブジェクトを扱うことができます。
// インターフェースの定義
interface Animal {
void makeSound(); // インターフェース内のメソッド
}
// インターフェースを実装するクラス
class Dog implements Animal {
public void makeSound() {
System.out.println("Woof!"); // Dog独自の実装
}
}
class Cat implements Animal {
public void makeSound() {
System.out.println("Meow!"); // Cat独自の実装
}
}
// メインメソッド
public class Main {
public static void main(String[] args) {
// インターフェース型の変数にクラスのオブジェクトを代入
Animal myDog = new Dog();
Animal myCat = new Cat();
// インターフェースのメソッドを呼び出す
myDog.makeSound(); // "Woof!" と出力
myCat.makeSound(); // "Meow!" と出力
}
}
こうすると、異なるクラスを同じ型として扱えるため、柔軟なコードを書けるようになります。
型とは何か
さて、上記の説明ではしれっと、変数の型からオブジェクトの型と言っていますが、そもそも型とは何でしょうか?
自分なりに分析した結果、型は下記のような役割を持つと理解しました。
- 宣言: メモリ領域にどのようなデータを保存するかを定める
- 型: メモリ領域に格納されたデータをどのように活用するかを定める
先述の例を参考に見てみましょう。
Animal animal = new Dog(); // Dog はAnimalを継承
これはメモリ上にDogとしてスペースを作っておくけど、変数に設定された型を通じてはAnimalとしてしかメモリの中身を見れないよ、ということを意味します。下記のようなイメージです。
型は目的物をどのように見るかを決めるフィルターレンズのようなものです。フィルターレンズを変更することで、親クラスにある共通メソッドのみを取り扱ったり、インターフェース経由で特有のメソッドのみに焦点をあてることが可能になります。
なぜ最初から親クラスで宣言しないのか
ポリモーフィズムの話を聞いたとき、異なるクラスで共通のメソッドが扱えるのがメリットなら最初から親クラスで宣言すればとも思いましたが、それだとメモリ領域に格納されるデータが親クラスの分しか確保されないんですね。
上記のように子クラスから親クラスへとクラスを変換することは可能です。なぜなら子クラスは親クラスのデータ+子クラス特有のデータで構成されているから。でもその逆はできない。そこで、メモリ領域にはデータをあらかじめ子クラス分も格納しておきつつ、クラスから親クラスへとアップキャストすることで、子クラス特有のメソッドを使うことができるというわけです。
インターフェースの場合
インターフェースも型であり、オブジェクトのメモリ領域に入っているデータをどのように処理するかを決めるフィルターレンズの役割であるという考え方は、クラスと共通しています。また実際にメソッドを実行する際には変数の型ではなく、オブジェクトの型に基づいて動作します(いわゆる動的バインディング)これも変数の型はあくまでもフィルターで、フィルタリングされた先のオブジェクトのデータを使うからなのだと考えれば、理解がしやすくなりました。
まとめ
ポリモーフィズムの考え方では、共通の処理を統一して処理したいけど、個別の特性も利用したいといった状況に適用できます。私自身は、その処理メカニズムが分からず調査をしていく中で、メモリ上のデータはオブジェクトの型で宣言された領域が不変であり、変数の型がメモリ領域の読み取り方を制御しているだけなのだと気づけたのは大きな収穫でした。
Discussion