ポリモーフィズム
□3. ポリモーフィズムに関する説明として正しいものを選びなさい。(1つ選択)
A. 実行時に決定する単一の型で、コンパイル時に決定する複数の型を扱うことができる仕組みである
B. 「has-a関係」で構成される複数の型において成立する仕組みである
C. クラスの継承によってのみ実現される
D. 具体的な動作はメソッドのオーバーライドによって実現される
解説
ポリモーフィズム(Polymorphism)はプログラミング言語の型システムに関連する用語として広い意味で使用されていますが、オブジェクト指向およびJavaプログラミングにおける文脈では、コンパイル時に決定する単一の型(静的な型)で、実行時における複数の型(動的な型)を扱うことができるという考え方や仕組みとして捉えることができます。
ポリモーフィズムの考え方が適用される型を「ポリモーフィックな型」と呼ぶ、is-a関係によって構成されます。
is-a関係は、Javaでは以下に挙げる2種類の仕組みによって実現されます。
・インターフェースの実装(A implements B)
・クラスおよびインターフェースの継承(A extends B)
このように、クラスの継承だけでなく、インターフェースの継承や実装も「is-a関係」を構成するために用いられます。
例として、以下に示すような継承関係のある型(クラスA、B、C)を考えてみましょう。
例. 継承関係クラスA、B、C
class A {}
class B extends A {}
class C extends A {}
この場合、3つの型はいずれもA型とみなすことができるため、以下のコードはすべて妥当なコードです。
例. A型のオブジェクトの生成
A obj = new A();
A obj = new B();
A obj = new C();
この例の代入演算子「=」の左辺式である変数宣言「A obj」において、型「A」はコンパイル時に決定する静的な型です。それに対して右辺式であるインスタンス生成のコードにおける型「A」、「B」、「C」は、実行時に決定する動的な型となります。このコード例では、「A」という単一の静的な型で3つの「A」、「B」、「C」という動的な型を扱うことができることがわかります。このことから冒頭のポリモーフィズムの説明は、「実行時におけるさまざまなオブジェクトを、すべて同じ種類のオブジェクトとみなして扱うことができる」と言い換えることができます。
【試験対策】ポリモーフィズムを単一の型におけるオーバーロードで実装される「静的ポリモーフィズム(Static Polymorphism)」と、複数の型におけるオーバーライドで実装される「動的ポリモーフィズム(Dynamic Polymorphism)」の2種類に分ける考え方もありますが、試験対策としては「is-a関係」と「オーバーライドによる動的ポリモーフィズム」の意味でポリモーフィズムを理解しておけば問題ありません。
ポリモーフィズムのベースにはこのようなis-a関係が不可欠です、実際にポリモーフィズムが威力を発揮するのは、メソッドのオーバーライドを利用する場合です。先ほどの例の3つの型「A」、「B」、「C」において、「doIt」というメソッドをオーバーライドしてみましょう。
例. メソッドのオーバーライド
class A {
void doIt() {
System.out.println("A.doIt()");
}
}
class B extends A {
void doIt() { // オーバーライド
System.out.println("B.doIt()");
}
}
class C extends A {
void doIt() { // オーバーライド
System.out.println("C.doIt()");
}
}
そして以下の例のように、あるクラスにおいて、A型の引数を取るメソッドを宣言することができます。
例. A型の引数を取るメソッドの宣言
class X {
void doSomething(A obj) {
obj.doIt();
}
}
このXクラスをコンパイルする時点では、doSomethingメソッドに渡される実際のオブジェクトが何であるかはわかりません。ただし、そのオブジェクトがA型であるということだけは決まっており、それゆえにdoItメソッドを呼び出すコードを書くことができます。また、そのdoItメソッドが具体的にどのような振る舞いをするかは関知する必要はありません。期待される振る舞いを提供するのは、あくまでもdoItメソッド実装側の責任となります。
このXクラスのdoSomethingメソッドを呼び出すコードは以下のようになり、引数にはA、B、C、いずれの方のオブジェクトでも渡すことができます(これらは実行時に生成されるインスタンスであり、動的な型として扱われます)。
例. オーバーライド・メソッドの利用
X x = new X();
x.doSomething(new A()); // -> A.doIt()
x.doSomething(new B()); // -> B.doIt()
x.doSomething(new C()); // ->C.doIt()
このようにポリモーフィズムを設計に導入することで、ライブラリの汎用性や再利用性を向上させることができます。実際に、デザイン・パターンの多くはポリモーフィズムを最大限に活用した設計となっています。