プログラミング応用a 第11回 『継承の基礎1』 11-2. 継承の基本 |
【11-2. 継承の基本】
継承(inheritance)を理解するために「集合」の考え方をおさらいしましょう。
【クラス型と集合】
我々が学習したクラスは,特定の条件を満たすオブジェクトの集合を表していると考えられます。
たとえば, “2で割ったら1余る整数”という条件を満たす数の集まりは『奇数』の集合ということになります(Fig.1-(2))。
なお,集合を図示するときには,Fig.1-(2)のように,要素を囲んだ線で表します。
実は,“クラスは集合,オブジェクトは要素”と考えることができます。たとえば,List 7のクラスA(List 7-①)は,
int型のフィールド i と,引数も返値も無いメソッド m( ) を持っています。 そして,List 7-②では A型のオブジェクト
を生成しています。
これを集合の考え方で言い表してみましょう。まず,クラスAの定義(List 7-①)は,
『“int型のフィールドfと,引数も返値も無いメソッドm()を持っている”という条件を満たすオブジェクトの集合A』
を定義しているものと考えられます。
そして,Aという集合の具体的な要素であるオブジェクトa,b,c,dを生成しているのが,List 7-②の部分であるというわけです。
このクラスAとそのオブジェクト a, b, c, dの関係を集合の考えで表現した図がFig.2です。
“クラスは集合,オブジェクトは要素”と考えることができるということがお分かりでしょう。
List 7
【スーパーセットとサブセット】
さて,集合の世界では,“集合が集合を含むという階層構造”になる場合がでてきます。
|
たとえば,『実数』の集合は『整数』という集合を含みますし,その『整数』の集合は『奇数』の集合を含みます (つまり,『奇数』の集合は『実数』の集合に含まれていることになる)。この関係をFig.3 に示します。 一般に,AとBという2つの集合があり,AがBを完全に含んでいる場合,“AはBのスーパーセット”と言われます。 また逆に,“BはAのサブセット(部分集合)”であると言います(Fig.4)。 Fig.3の例では,『実数』は『整数』のスーパーセット,逆に『整数』は『実数』のサブセットです。 また,『整数』は『奇数』のスーパーセット,逆に『奇数』は『整数』のサブセットになっています。 そしてもちろん,『実数』は『奇数』のスーパーセットであり,『奇数』は『実数』のサブセットです。 サブセットの条件は,スーパーセットの条件に独自の条件を追加したものになっていて,スーパーセットの条件 よりも厳しくなっています。 Fig.3の例では,『整数』集合の条件は, “実数であり” とスーパーセットの条件をあげて,さらに “かつ小数点以下が無い数の集まり” と独自の条件が追加されています。 つまり,サブセットの要素は常にスーパーセットの条件を満たしていて,さらにその上サブセット独自の追加条件 を満たしているわけです。ですから,サブセットはスーパーセットの特殊な場合を表現していることになります。 Fig.3の例で言えば,『整数』とは,『実数』のうち,“小数点以下が無い”という特殊な例を示しているわけです。 言い換えれば,“サブセットはスーパーセットの一種である”ということです。Fig.3 の例では, “『整数』は『実数』の一種である”, “『奇数』は『整数』の一種である” となります。この,“XはYの一種である”という文は英語では X is a Y と表現します。ここで冠詞のaには“〜の一種”という意味があるのです。この表現を使うと, サブセットと スーパーセットの関係は サブセット is a スーパーセット と書くことができます。そして,このサブセットとスーパーセットの関係を is-a関係 と呼びます。 |
---|
【継承とis-a関係】
前述したように,
“サブセットはスーパーセットの一種”
であり, Fig.3 の例では ,“『奇数』は『整数』の一種”と言えます。実際,奇数である 7 は整数でもあるので,
整数として扱うことができます。
さて,Fig.2 などで説明したように,クラスは集合として考えることができます。そして,実はJavaでは,
スーパーセットとサブセットに相当するクラスを作成できるのです。
スーパーセットに相当するクラスを, スーパークラス(super class),
サブセットに相当するクラスを, サブクラス(sub class) と呼びます。
あるクラスのサブクラスを作成するには,Fig.5 のようにクラスの定義時に,クラス名の直後で予約語extends
を使って
extends スーパークラス名
というようにスーパークラスを指定するだけです。
上図のように,サブクラスはスーパークラスのメンバをすべて持っている(継承する)ことになります。
実際に継承を行うと,どのようなことがおこるかを, List 8 で説明しましょう。
あるクラスのサブクラスがどのような性質を持っているかを,簡単なプログラムで調べてみましょう。List 8を見て下さい。
List 8-①はフィールドiとメソッド m( ), h( ) を持つクラスAを定義しています。
そして,List 8-②で,AをスーパークラスとするサブクラスBを定義しています。
その定義の中では,フィールドjとメソッド g( ) だけを定義しています。
List 8-③で定義されているメソッド print( ) は, A型の仮引数を持っていて,仮引数の A型オブジェクトのフィールドiの
値を表示し,同じく仮引数の A型オブジェクトのメソッド m( ) と h( ) を呼んでいます。
さて,動作テスト用の main( ) メソッドでは,まずList 8-④でB型オブジェクトbを生成しています。
そして,List 8-⑤ではなんとB型オブジェクトbのメンバとして i, m( ), h( ) を使用しています。
List 8-<②>のB型の定義を見ると,B型のメンバは j と g( ) しかないはずです。一体これはどうなって
いるのでしょう。
実は,サブクラスは,スーパークラスの全メンバを自動的に受け継ぐのです(Fig.6)。
このことを,“サブクラスはスーパークラスのメンバを継承する”などと言います。
クラスBのスーパークラスはクラスAなので,クラスB はクラスAのメンバ i, m( ), h( ) を受け継いでいるのです。
そのため,List 8-⑤のように B型オブジェクトでもメンバ i, m( ), h( ) を利用することができるわけです。
もちろん,B型オブジェクトはA型オブジェクトのメンバだけでなく,B型の定義(List 8-<②>)で定義した独自のメンバ j と g( ) も
持っています(Fig.6)。List 8-⑥では,クラスBに独自のメンバ j と g( ) を利用している例です。
さて,次のList 8-⑦はもっとびっくりするような内容です。この部分は
A a = new B();
となっていて,B型オブジェクトを生成して,それをA型変数へ代入しています。この状態をFig.7 に示します。
このように,実はJavaではスーパークラス型の変数にサブクラス型のオブジェクトを代入できるのです。
このため,Fig.7のように参照型変数の型とそれが指している実際のオブジェクトの型が異なることがあるわけです。
変数の型は宣言時(つまりコンパイルする時)に決まり,決まるので静的な型と呼ばれます。
一方,実際のオブジェクトの型は
A a = new A(); // ここではaが参照しているオブジェクトはA型
a = new B(); // ここではaが参照しているオブジェクトはB型
というようにプログラムの実行時に変化しうるので,動的な型と呼ばれます。
List 8
続いてList 8-⑧では
a.i = 3;
a.m( );
a.h( );
というように,変数a を通じてメンバを操作することができています。変数aの型(A)とそれが指している実際のオブジェクトの型(B)
が異なっているにもかかわらず,です。
なぜこのようなことが許されるのでしょうか。ここで先ほどの集合の話しを思い出してみましょう。“サブセットはスーパーセットの一種”
という関係(is-a関係)が成立していましたね。
実は,サブクラスとスーパークラスでも,“サブクラスはスーパークラスの一種”というis-a関係が成立しているのです。
つまり,Javaコンパイラは,サブクラスのオブジェクトを,スーパークラスのオブジェクトの一種として扱ってくれるのです。
考えてみれば,サブクラスのオブジェクトは,スーパークラスのメンバをすべて継承しているわけです。たとえば,List 8のクラスB は,
クラスA のメンバ i, m( ), h( ) をすべて継承しています。ですから,サブクラスのオブジェクトをスーパークラスのオブジェクトとして
扱ってもなんら問題はないのです(Fig.8)。
ですから,サブクラスのオブジェクトを,スーパークラス型の仮引数を持つメソッドの実引数にすることも可能です(List 8-⑩)。
なお,オブジェクトへの操作(メンバへのアクセス)は,その静的な型によって制限されます。たとえばList 8 のクラスA, B の場合を
考えてみましょう。
A a = new B( );
というように,変数aの静的な型がA,動的な型がBであるとします。このとき,aに対する操作はその静的な型であるAに制限されます。
したがってA型で定義されているメンバ i, m( ), h( ) には
a.i, a.m( ), a.h( )
というようにaを介してアクセスできます(List 8-⑧)。しかし,B型独自のメンバ j, g( ) には aを介して
a.j, a.g()
というようにアクセスすることはできません。List 8-⑨では
a.j = 10;
a.g( );
という2つの文がコメントアウトされていますが,この部分を有効にしてList 8をコンパイルするとエラーになります。Javaコンパイラに
とっては, aはあくまでA型の変数であるので,「A型のメンバにしかアクセスできないよ」ということになってエラーとなるわけです。
ところで,JavaではスーパークラスAのサブクラスBを定義するときには前述したように
class B extends A {
// ここにB独自のメンバ定義
}
と書きますが,この予約語extendsは“拡張する”という意味の英語です。つまり,
class B extends A
とは,“クラスBはクラスAを拡張する”という意味なのです。