クラスの継承と参照型変数

みるくあいらんどっ! > ドキュメント > Java > じっくり学ぶ Java講座 [初心者向け・入門]


クラスの継承と参照型変数

これまでのプログラムでは、インスタンスと参照型変数の型は常に一致していました。例えば、以下のような感じです。

		WiseDog wiseDog = new WiseDog("ラッキー");

WiseDog型インスタンスを作成し、それを WiseDog型の参照型変数に紐づけていました。

実は、参照型変数はインスタンスのスーパークラス側の参照型変数でも紐づけることができます。具体的には以下の感じです。

		Dog dog = new WiseDog("ラッキー");
		Object object = new WiseDog("ハチ", 10);

本来の型とは異なって、スーパークラス側の参照型変数で紐づけた場合の重要な点を 3つ挙げます。

  • 作られるインスタンスは指定した型のもので、参照型変数の型には影響されない(上記の 3例いずれも、WiseDog型インスタンスが生成される。Dog型インスタンスや Object型インスタンスは生成されない)。
  • 参照型変数の参照型(上の例では Dogや Object)クラスで宣言されていないフィールドやメソッドを利用することはできず、ビルドエラーになる。これはこの後で実験してみます。どうしても利用したい場合は、明示的なキャストが必要になりますが、これについては次の節で説明します。
  • メソッドを利用した場合、インスタンスの型に対応したメソッドが実行される。参照型変数の型には影響されない。これについても、この後で実験してみます。

そのインスタンスに特有のメソッドを利用する場合には、インスタンスの型と一致した参照型変数を利用するのが最も楽です。それなのに、なぜ、このような分かりづらく、かつ敢えて機能を制限するようなことをするのかと言うと、それによってプログラムがより柔軟になるから。オブジェクト指向の概念で重要な要素である多態性を理解するためには、「作成したインスタンスとそれを参照する型が異なる」という状態が起こり得ることを理解できなければなりません。なお、多態性については、後の節で解説します。

理論的なことを説明しても難しいだけです。具体的なソースコードを見ていきましょう。

クラスの継承と参照型変数の実際

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クラスについて、以下の数を正確に数えられるでしょうか。

  • Dogクラス
    • 宣言された staticなフィールド…0個
    • 宣言された staticなメソッド…0個
    • 宣言されたインスタンスフィールド…2個
    • 宣言されたインスタンスメソッド…5個(そのうち 1個はオーバーライド)
    • コンストラクタ…2種類
  • WiseDogクラス
    • 宣言された staticなフィールド…0個
    • 宣言された staticなメソッド…0個
    • 宣言されたインスタンスフィールド…0個(Dogクラスで宣言されたものも合算すると 2個)
    • 宣言されたインスタンスメソッド…3個(Dogクラスで宣言されたものも合算すると 6個。5 + 3 = 6になるのは、2個のメソッドがオーバーライドされているため)
    • コンストラクタ…2種類

以下のことが理解できるでしょうか。

  • 作成されたインスタンスは WiseDog型が 2個。Dog型や Object型のインスタンスは生成されていない。
  • メインメソッドでは、WiseDog型の参照型変数が 1個、Dog型の参照型変数が 2個、Object型の参照型変数が 1個宣言されている。
  • 1番目に作成した WiseDog型インスタンス(「ラッキー」のほうは)、1つの参照型変数に紐づけられている。2番目に作成した WiseDog型インスタンス(「ハチ」のほう)は、3つの参照型変数に紐づけられている。
  • dog1.cry();dog2.cry();によって表示される文字は「ワン~」ではなく、「キャン~」である。すなわち、WiseDog型のインスタンスメソッド cryが実行されている。そもそもインスタンスは WiseDog型なのだから、このような動きとなる。
  • 同様に、dog2object2のインスタンスの内容を表示する際も、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ヶ所隠れていました。

なお、参照型のキャストは、基本型のキャストとはまったく意味合いが異なります。そのため、次の節にてじっくりと解説していくこととします。本節に登場した省略可能なキャストについては、「暗黙的なキャスト」部を参照ください。

本ウェブサイトでは、この「暗黙的なキャスト」については、一貫して省略することとします。

最終更新: 2013/02/24 , 公開: 2013/02/24
▲top