これまでのプログラムでは、インスタンスと参照型変数の型は常に一致していました。例えば、以下のような感じです。
WiseDog wiseDog = new WiseDog("ラッキー");
WiseDog型インスタンスを作成し、それを WiseDog型の参照型変数に紐づけていました。
実は、参照型変数はインスタンスのスーパークラス側の参照型変数でも紐づけることができます。具体的には以下の感じです。
Dog dog = new WiseDog("ラッキー"); Object object = new WiseDog("ハチ", 10);
本来の型とは異なって、スーパークラス側の参照型変数で紐づけた場合の重要な点を 3つ挙げます。
そのインスタンスに特有のメソッドを利用する場合には、インスタンスの型と一致した参照型変数を利用するのが最も楽です。それなのに、なぜ、このような分かりづらく、かつ敢えて機能を制限するようなことをするのかと言うと、それによってプログラムがより柔軟になるから。オブジェクト指向の概念で重要な要素である多態性を理解するためには、「作成したインスタンスとそれを参照する型が異なる」という状態が起こり得ることを理解できなければなりません。なお、多態性については、後の節で解説します。
理論的なことを説明しても難しいだけです。具体的なソースコードを見ていきましょう。
Dogクラスと WiseDogクラスを利用して、参照型変数とインスタンスの関係についていろいろ実験しようと思います。実験をより分かりやすくするために、Dogクラス、WiseDogクラスともに toStringメソッドをオーバーライドしています。
ソースコードは以下の通り。
M301/Dog.java
/** * 犬を表すクラスです。 */ public class Dog { /** * 名前。 */ protected String name; /** * 鳴く回数。 */ protected int cryCount; /** * コンストラクタ。 * @param name 名前 */ public Dog(String name) { this(name, 1); } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public Dog(String name, int cryCount) { this.setName(name); this.setCryCount(cryCount); } /** * 名前を設定します。 * @param name 名前 */ public void setName(String name) { this.name = name; } /** * 鳴く回数を設定します。 * @param cryCount 鳴く回数 */ public void setCryCount(int cryCount) { this.cryCount = cryCount; } /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ワン"); } System.out.println("」"); } /** * お座りします。 */ public void sitDown() { System.out.println(this.name + "は座りました。"); } @Override public String toString() { return "[Dog] name:" + this.name + ", cryCount:" + this.cryCount; } }
M301/WiseDog.java
/** * 賢い犬を表すクラスです。 */ public class WiseDog extends Dog { /** * コンストラクタ。 * @param name 名前 */ public WiseDog(String name) { super(name); } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public WiseDog(String name, int cryCount) { super(name, cryCount); } @Override public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("キャン"); } System.out.println("」"); } /** * お手をします。 */ public void touch() { System.out.println(this.name + "はお手をしました。"); } @Override public String toString() { return "[WiseDog] name:" + this.name + ", cryCount:" + this.cryCount; } }
M301/M301.java
/** * クラスの継承と参照型変数について確認します。 */ public class M301 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { // インスタンスを作成 Dog dog1 = new WiseDog("ラッキー"); WiseDog wiseDog2 = new WiseDog("ハチ", 10); Dog dog2 = wiseDog2; Object object2 = wiseDog2; // 「ラッキー」のインスタンスメソッドを実行 System.out.println("内容は " + dog1); dog1.cry(); // dog1.touch(); // ビルドエラー // 「ハチ」のインスタンスメソッドを実行 System.out.println("---"); System.out.println("内容は " + wiseDog2); wiseDog2.cry(); wiseDog2.touch(); System.out.println("---"); System.out.println("内容は " + dog2); dog2.cry(); // dog2.touch(); // ビルドエラー System.out.println("---"); System.out.println("内容は " + object2); // object2.cry(); // ビルドエラー // object2.touch(); // ビルドエラー } }
実行結果は以下の通り。
内容は [WiseDog] name:ラッキー, cryCount:1 ラッキー「キャン」 --- 内容は [WiseDog] name:ハチ, cryCount:10 ハチ「キャンキャンキャンキャンキャンキャンキャンキャンキャンキャン」 ハチはお手をしました。 --- 内容は [WiseDog] name:ハチ, cryCount:10 ハチ「キャンキャンキャンキャンキャンキャンキャンキャンキャンキャン」 --- 内容は [WiseDog] name:ハチ, cryCount:10
まず、数を数えられることが大切です。Dogクラスと WiseDogクラスについて、以下の数を正確に数えられるでしょうか。
以下のことが理解できるでしょうか。
dog1.cry();
やdog2.cry();
によって表示される文字は「ワン~」ではなく、「キャン~」である。すなわち、WiseDog型のインスタンスメソッド cryが実行されている。そもそもインスタンスは WiseDog型なのだから、このような動きとなる。dog2
やobject2
のインスタンスの内容を表示する際も、WiseDog型インスタンスに関する情報が表示される。すなわち、WiseDog型のインスタンスメソッド toStringが実行されている。そもそもインスタンスは WiseDog型なのだから、このような動きとなる。dog1.touch();
やdog2.touch();
はビルドエラーになる。参照型変数 dog2の参照先インスタンスは確かに WiseDog型なのだけれど、dog2は Dog型であり、Dog型にはそのようなメソッドは宣言されていないのでビルドエラーになってしまう。object2.cry();
やobject2.touch();
はビルドエラーになる。参照型変数 object2の参照先インスタンスは確かに WiseDog型なのだけれど、object2は Object型であり、Object型にはそのようなメソッドは宣言されていないのでビルドエラーになってしまう。イメージとしては以下のような感じです。
以下のようにインスタンスをそのスーパークラス側の参照型変数で紐づけることができました。
Dog dog1 = new WiseDog("ラッキー"); WiseDog wiseDog2 = new WiseDog("ハチ", 10); Dog dog2 = wiseDog2; Object object2 = wiseDog2;
実は、ここにはキャストが省略されており、省略をせずに表現すれば以下になります。
Dog dog1 = (Dog)new WiseDog("ラッキー"); WiseDog wiseDog2 = new WiseDog("ハチ", 10); Dog dog2 = (Dog)wiseDog2; Object object2 = (Object)wiseDog2;
このように参照型のキャストが 3ヶ所隠れていました。
なお、参照型のキャストは、基本型のキャストとはまったく意味合いが異なります。そのため、次の節にてじっくりと解説していくこととします。本節に登場した省略可能なキャストについては、「暗黙的なキャスト」部を参照ください。
本ウェブサイトでは、この「暗黙的なキャスト」については、一貫して省略することとします。