#pragma twice

KAB-studio > プログラミング > Javaのオブジェクト指向入門 > 5. 継承で拡張しよう > 5.5 スーパーからサブは見えません
 
前のページへつぎ

5.5 スーパーからサブは見えません

del.icio.us 登録する はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数 livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数 Yahoo!ブックマーク 詳細を表示 users RSSに登録
更新日: 2008/04/07
動作確認環境:Windows XP Professional SP2, Java SE 5

スーパークラスからサブクラスは?

 ここまでスーパークラスとサブクラスの関係を見てきました。
 おそらく皆さんの中には「サブクラスのインスタンスの中に、スーパークラスのインスタンスが入っている」というイメージがしっかりできたと思います。
 ……が、ひとつ注意して欲しいことがあります。
 それはスーパークラスからサブクラスは見えていないということです。

// NonOverrideSubRunner.java

/**
 * 普通のクラス。
 * メソッドを1つ持ちます。
 */
class UseSubClassSuper
{
    /**
     * printMyName()メソッドを呼び出すメソッドです。
     */
    void callPrintMyName()
    {
        // サブクラスのprintMyName()メソッドを……?
        printMyName();
        // コンパイルエラー:
        // NonOverrideSubRunner.java:15: シンボルを見つけられません。
        // シンボル: メソッド printMyName()
        // 場所    : UseSubClassSuper の クラス
        //           printMyName();
        //           ^
    }
}

/**
 * CallSubClassSuperクラスから継承した、サブクラス。
 * メソッドを追加しています。
 */
class NonOverrideSub extends UseSubClassSuper
{
    /**
     * 自クラスの名前を出力するメソッドです。
     * オーバーライドしていません。
     */
    void printMyName()
    {
        System.out.println( "NonOverrideSub" );
    }
}
// NonOverrideSubRunner.java
/**
 * 普通のクラス。
 * メソッドを1つ持ちます。
 */
class UseSubClassSuper
{
	/**
	 * printMyName()メソッドを呼び出すメソッドです。
	 */
	void callPrintMyName()
	{
		// サブクラスのprintMyName()メソッドを……?
		printMyName();
		// コンパイルエラー:
		// NonOverrideSubRunner.java:15: シンボルを見つけられません。
		// シンボル: メソッド printMyName()
		// 場所    : UseSubClassSuper の クラス
		//           printMyName();
		//           ^
	}
}
/**
 * CallSubClassSuperクラスから継承した、サブクラス。
 * メソッドを追加しています。
 */
class NonOverrideSub extends UseSubClassSuper
{
	/**
	 * 自クラスの名前を出力するメソッドです。
	 * オーバーライドしていません。
	 */
	void printMyName()
	{
		System.out.println( "NonOverrideSub" );
	}
}

 このプログラムは、前ページのプログラムとほぼ同じです。
 違うのは「スーパークラスにprintMyName()メソッドがない」という点です。

 まず、UseSubClassSuperクラスを用意します。

/**
 * 普通のクラス。
 * メソッドを1つ持ちます。
 */
class UseSubClassSuper
{
    /**
     * printMyName()メソッドを呼び出すメソッドです。
     */
    void callPrintMyName()
    {
        // サブクラスのprintMyName()メソッドを……?
        printMyName();
    }
}
/**
 * 普通のクラス。
 * メソッドを1つ持ちます。
 */
class UseSubClassSuper
{
	/**
	 * printMyName()メソッドを呼び出すメソッドです。
	 */
	void callPrintMyName()
	{
		// サブクラスのprintMyName()メソッドを……?
		printMyName();
	}
}

 このクラスのcallPrintMyName()メソッドは、printMyName()メソッドを呼び出しています。
 でもprintMyName()メソッドはUseSubClassSuperクラスにありません。
 代わりに、サブクラスのNonOverrideSubクラスにあります。

/**
 * CallSubClassSuperクラスから継承した、サブクラス。
 * メソッドを追加しています。
 */
class NonOverrideSub extends UseSubClassSuper
{
    /**
     * 自クラスの名前を出力するメソッドです。
     * オーバーライドしていません。
     */
    void printMyName()
    {
        System.out.println( "NonOverrideSub" );
    }
}
/**
 * CallSubClassSuperクラスから継承した、サブクラス。
 * メソッドを追加しています。
 */
class NonOverrideSub extends UseSubClassSuper
{
	/**
	 * 自クラスの名前を出力するメソッドです。
	 * オーバーライドしていません。
	 */
	void printMyName()
	{
		System.out.println( "NonOverrideSub" );
	}
}

 printMyName()メソッドはUseSubClassSuperクラスにはありませんから、これはオーバーライドしているわけではありません。
 単純に、継承してprintMyName()メソッドを追加しただけ、ということです。

 さてこの時どうなるかというと、実行結果がどうという以前にコンパイルエラーになります

/**
 * 普通のクラス。
 * メソッドを1つ持ちます。
 */
class UseSubClassSuper
{
    /**
     * printMyName()メソッドを呼び出すメソッドです。
     */
    void callPrintMyName()
    {
        // サブクラスのprintMyName()メソッドを……?
        printMyName();
        // シンボル: メソッド printMyName()
        // 場所    : UseSubClassSuper の クラス
        //           printMyName();
        //           ^
    }
}
/**
 * 普通のクラス。
 * メソッドを1つ持ちます。
 */
class UseSubClassSuper
{
	/**
	 * printMyName()メソッドを呼び出すメソッドです。
	 */
	void callPrintMyName()
	{
		// サブクラスのprintMyName()メソッドを……?
		printMyName();
		// シンボル: メソッド printMyName()
		// 場所    : UseSubClassSuper の クラス
		//           printMyName();
		//           ^
	}
}

 このように、printMyName()メソッドを呼び出している箇所でコンパイルエラーになります。
 なぜかというと、「UseSubClassSuperクラスにはprintMyName()メソッドがない」からです。

 これまで説明してきたイメージは、すべて実行時のイメージです。
 実行時にはインスタンスが作られます。これまで見てきたとおり、サブクラスのインスタンスの中に、スーパークラスのインスタンスが入っている、というイメージが作られます。
 ですが、実行する前にまずコンパイルする必要があります。
 コンパイラがUseSubClassSuperクラスをコンパイルするとき、callPrintMyName()メソッドの中でprintMyName()メソッドの呼び出しを見つます。
 コンパイル時点ではもちろんインスタンスは存在しません。だから「サブクラスの中に~」みたいなことは全く関係ありません。
 そのため、コンパイラは「printMyName()メソッド? そんなもんねーよ!」となり、コンパイルエラーを出します。

 このように、コンパイルする時と実行時のイメージは全く別です。
 コンパイル時にはサブクラスとの関係は分かりませんから、スーパークラスからサブクラスを使うことはできないわけです。

オーバーライドに気付かない

 これまで説明してきた「オーバーライド」を使うことで、スーパークラスのメソッドの代わりにサブクラスのメソッドを呼び出させることができました。
 でも、それは「スーパークラスから見た場合、気づかないこと」だということに注意してください。
 スーパークラスから「オーバーライドされたメソッド」を呼び出している箇所では、特別なことをしていません。見た目上は、同じスーパークラスのメソッドを呼んでいるだけです。別に「サブクラスのメソッドを呼び出す!」という指定をしているわけではありません。呼び出してみたら、結果サブクラスのメソッドが呼び出された、ということなのです。
 前ページの例で確認してみましょう。

 HasTwoMethodSuperクラスのcallPrintMyName()メソッドがOneMethodOverridedSubクラスのprintMyName()メソッドを呼び出しています。
 でも、HasTwoMethodSuperクラスのcallPrintMyName()メソッドは同じクラスのprintMyName()メソッドを呼び出しているつもりです。意図して「サブクラスのメソッドを呼び出している」わけではありません

 オーバーライドによって継なげられる「ひも」は、インスタンスの作成時にサブクラスが勝手に付けるという形になります。
 スーパークラスはそのことに気付きません
 スーパークラスは、スーパークラス単体で動いてきた時と同じように動作しようとしますが、サブクラスによって継なげられた「ひも」によって、気付かない内に処理を変えられているというわけです。
 スーパークラスとサブクラスは、インスタンスで「一緒になっている」というイメージがありますが、あくまで別々。スーパークラスはサブクラスを関知しないということに注意してください。

使えなくもないよ?

 前ページとこのページの内容は、次の次の章で説明する「ポリモーフィズム」という機能の布石です。
 その章で説明しますが、実は「ダウンキャスト」というものを使うと、スーパークラスからサブクラスを使うことができます。
 その時まで前ページとこのページの内容を覚えておきましょう。

5.5 スーパーからサブは見えません
このページは、Java言語を用いたオブジェクト指向プログラミングのチュートリアル解説を行う「Javaのオブジェクト指向入門」の一ページです。
詳しい説明は「Javaのオブジェクト指向入門」目次をご覧ください。