ポリモーフィズム
ポリモーフィズム(polymorphism)は、オブジェクト指向プログラにおける概念のひとつです。日本語では「多態性」「多様性」など呼ばれていますが、このままではイメージが掴めません。
英文の意味に立ち戻ると「poly」には「複数の」であり、「morph」とは「姿を変える」という意味ですから、ポリモーフィズムは一つの事を複数の違った形で扱えたり、類似したいろいろな物を同じものとして扱える概念です。
ポリモーフィズムによって、プログラムをブラックボックのまま、より柔軟に扱うことが出来ます。
ポリモーフィズムは概念ですから、具体的な機能はいくつか考えられます。
例えば、継承とオーバーライドによって同じメソッド呼び出しであっても、常に同じふるまいではなく、そのときの目的に適したふるまいを自動的に切り替えることが出来るのもポリモーフィズムです。
また、継承関係にある参照変数間の代入や、インスタンスのキャストもポリモーフィズムです。
継承関係にある参照変数間の代入
これはインスタンスを作成する時に、参照変数の型をインスタンスの型ではなく継承関係にあるスーパークラスの型にすれば、そのインスタンスはあたかもスーパークラスの型として扱えるという物です。
継承で使ったNormalTVとSmartTVに在庫情報を追加して、異なる製品を同じ配列で管理する場合を考えてみましょう。
public class NormalTV2 { String serialNo; String tvType; NormalTV2(String tvType, String serialNo) { this.tvType = tvType; this.serialNo = serialNo; } NormalTV2(String serialNo) { this.tvType = "NormalTV"; this.serialNo = serialNo; } void on() { System.out.println("\t電源を入れます。"); } void off() { System.out.println("\t電源を切ります。"); } void display() { System.out.println("\t番組を視聴します。"); } void channelChange() { System.out.println("\tチャネルを切り替えます。"); } void functions() { on(); off(); display(); channelChange(); } void printProductInfo() { System.out.println(tvType + " : serial = " + serialNo); } }
サブクラス
public class SmartTV2 extends NormalTV2 { SmartTV2(String serialNo) { super("SmartTV ", serialNo); } void internet() { System.out.println("\tインターネットにアクセスします。"); } void recode() { System.out.println("\t番組を録画します。"); } void dvd() { System.out.println("\tDVDを再生します。"); } void functions() { super.functions(); internet(); recode(); dvd(); } }
スーパークラス、サブクラスを一緒に管理する処理
public class PolymorphismSample { public static void main(String[] args) { NormalTV2[] tvs = { new NormalTV2("0001"), new SmartTV2("0101"), new NormalTV2("0002"), new NormalTV2("0003"), new SmartTV2("0101") }; for(NormalTV2 tv : tvs) { tv.printProductInfo(); } } }
実行結果
>java PolymorphismSample NormalTV : serial = 0001 SmartTV : serial = 0101 NormalTV : serial = 0002 NormalTV : serial = 0003 SmartTV : serial = 0101 >
これにより、継承関係にあるいろいろな実装を同じ型として扱えますが、インスタンスがどのようなメソッドやフィールドを持っていたとしても、扱っている型で定義されているもの以外は使えません。使おうとするとコンパイルエラーになります。
このポリモーフィズムが成り立つのは、継承関係だけでなく実現関係でも構いません。継承関係も実現関係もない場合にはポリモーフィズムは成立せずコンパイルエラーになります。
※インターフェースはインスタンス化が行えないので、インターフェース自体をポリモーフィズムで扱う事はできません。
インスタンスのキャスト
サブクラスをスーパークラス型に変換するのをアップキャストと呼びます。アップキャストは、型の互換性チェックが簡単に行えるので自動的に行われます。
一方、スーパークラス型で扱っていたインスタンスを、元の型に戻すのはダウンキャストと呼びます。ダウンキャストは型の互換情報が無いので、明示的にキャスト指定する必要があります。
インスタンスが扱えるのは参照変数の型に定義されているものだけです。そこで、アップキャストして作ったサブクラスのインスタンスをダウンキャストすると、サブクラスの差分として定義したメソッドやサブクラスで定義したフィールド(スーパークラスのフィールドと同名の変数がサブクラスにある場合には変数の実体が切り替わる)が使えるようになります。しかし、スーパークラスをダウンキャストしてサブクラスの差分のフィールドやメソッドにアクセスすると実行時に例外が発生します。
アップキャスト
public class UpCastSample { public static void main(String[] args) { NormalTV2 tv = new SmartTV2("0101"); tv.printProductInfo(); tv.functions(); // tv.internet(); // tv.recode(); // tv.dvd(); } }
実行結果
>java UpCastSample SmartTV : serial = 0101 電源を入れます。 電源を切ります。 番組を視聴します。 チャネルを切り替えます。 インターネットにアクセスします。 番組を録画します。 DVDを再生します。 >
アップキャストした参照変数型に含まれるメソッドにアクセスする限りは実行できます。
(オーバーライドしたメソッドはオーバーライドした側のメソッドが使われます。)
しかし、ソースのコメントアウトを外して参照型変数型に含まれないメソッドにアクセスするとコンパイルエラーが出ます。
>javac UpCastSample.java UpCastSample.java:8: エラー: シンボルを見つけられません tv.internet(); ^ シンボル: メソッド internet() 場所: タイプNormalTV2の変数 tv UpCastSample.java:9: エラー: シンボルを見つけられません tv.recode(); ^ シンボル: メソッド recode() 場所: タイプNormalTV2の変数 tv UpCastSample.java:10: エラー: シンボルを見つけられません tv.dvd(); ^ シンボル: メソッド dvd() 場所: タイプNormalTV2の変数 tv エラー3個 >
ダウンキャスト
public class DownCastSample { public static void main(String[] args) { SmartTV2 tv = (SmartTV2) new NormalTV2("0001"); tv.printProductInfo(); tv.functions(); } }
実行結果
ダウンキャストを明示しない>場合は以下のコンパイルエラーになります。
>javac DownCastSample.java DownCastSample.java:4: エラー: 不適合な型: NormalTV2をSmartTV2に変換できません: SmartTV2 tv = new NormalTV2("0001"); ^ エラー1個 >
ダウンキャストを明示すると、コンパイルは通りますが実行時に例外が発生します。
>java DownCastSample Exception in thread "main" java.lang.ClassCastException: NormalTV2 cannot be cast to SmartTV2 at DownCastSample.main(DownCastSample.java:4) >
アップキャストしたものをダウンキャストする
public class UpDownCastSample { public static void main(String[] args) { NormalTV2 ntv = new SmartTV2("0101"); SmartTV2 stv = (SmartTV2) ntv; stv.printProductInfo(); stv.internet(); stv.recode(); stv.dvd(); } }
実行結果
アップキャスト時にはアクセスできなかったメソッドがアクセスできるようになります
>java UpDownCastSample SmartTV : serial = 0101 インターネットにアクセスします。 番組を録画します。 DVDを再生します。 >
つまり、インスタンスのキャストはインスタンスの扱い方が変わるので、参照先のインスタンスが変わるわけではありません。