クラスの継承の本質を知るいまから始めるJava(4)

» 2003年03月08日 00時00分 公開
[中野克平@IT]

 前回「クラスを簡単に理解する」まで、プリミティブ型とクラス型について説明してきました。コンピュータの中には結局のところCPUとメモリしかありません。「変数は箱だ」といった例え話よりも、変数がどのようにメモリを使うのかで考えた方が意外に分かりやすいものです。

 さて、今回はクラスの特徴的機能である「継承」について説明します。継承はJavaをはじめとするオブジェクト指向言語の代表的概念ですが、初学者には最初の壁となって立ちはだかります。まず、クラスについておさらいしましょう。

  • クラスには、メンバ変数としてプリミティブ型やほかのクラス型変数を要素として格納できる。
  • クラスのインスタンスを生成すると、メンバ変数を格納するのに必要なサイズのメモリ領域が確保される。

 前回は地図上の地点を表現するクラスとして、次のようなクラスを作りました。

class GeographicInfo {
  double latitude;
  double longitude;
}

 GeographicInfo型のクラスには、経度と緯度を表現するためにdouble型のlatitude(緯度)とlongitude(経度)というメンバ変数が定義されています。GeographicInfo型のクラスを使うことで、ある場所の経度と緯度を別個の変数として管理しなくてもよくなります。しかし、プログラムに修正はつきものです。経度と緯度のほかに高度も扱いたくなったとしたら、どうしたらよいでしょうか。最も簡単な解決策は、別のクラスを新しく定義することです。

class GeographicInfo3D {
  double latitude;
  double longitude;
  double height;
}

 この方法のデメリットは、GeographicInfo型を使って作った既存のプログラムと互換性がなくなることです。GeographicInfo型とGeographicInfo3D型はまったく関係のない別のクラスですので、元のコードとの互換性を保つには、ソースコードをすべて変更して、GeographicInfo3D型に置き換えなくてはいけません。

 別のデメリットもあります。GeographicInfoとGeographicInfo3Dは別のクラスですが、違いは「double height;」の1行だけです。これでは、クラス定義の3分の2が重複していることになります。複雑なクラスであれば、メンバ変数を1つ追加するためによく似たクラスを定義することの無駄はもっと多くなります。

クラスの継承とは定義の流用である

 そこでJavaのクラスには、「継承」という機能が用意されています。まずは実際のソースコードを見てみましょう。

class GeographicInfo3D extends GeographicInfo {
  double height;
}

 クラスの継承は、クラスの定義として「extends <元になるクラス名>」という文と追加するメンバ変数を実際に定義するだけです。

 それでは実際に2つのクラスを使ったプログラムを作ってみましょう。

   GeographicInfo3Dクラスを使うプログラム
class GeographicInfo {
  double latitude;
  double longitude;
}

class GeographicInfo3D extends GeographicInfo {
  double height;
}

public class UsingGeographicInfo3D {
  public static void main( String args[] ) {
      GeographicInfo3D g  = new GeographicInfo3D();
      g.latitude    = 35.66;
      g.longitude   = 139.75;
      g.height      = 10.0;

      System.out.println(g.latitude);
      System.out.println(g.longitude);
      System.out.println(g.height);
  }
}

 ちょっと長めのプログラムですが、やっていることは3つしかありません。まず、最初のブロックでは、GeographicInfo型のクラスを定義しています。次のブロックでは、GeographicInfo型を継承してGeographicInfo3D型を定義しています。最後のブロックはプログラム本体で、GeographicInfo3D型のインスタンスを変数gに代入し、メンバ変数latitude、longitude、heightに値を代入して画面に表示しています。それでは、念のためこのプログラムを実行してみましょう。

C:\DOCUME~1\MYDOCU~1\MYJAVA~1>java UsingGeographicInfo3D
35.66
139.75
10.0

C:\DOCUME~1\MYDOCU~1\MYJAVA~1>


 GeographicInfo3Dでは、メンバ変数としてheightだけを宣言しており、latitude、longitudeという変数を定義していません。その代わりに、GeographicInfo3D型がGeographicInfo型を継承している、と定義しています。そのため、GeographicInfo3D型の変数gを使って、GeographicInfo3D型で直接定義されていないlatitude、longitudeの値を表示できるわけです。

 Javaでは、GeographicInfo型のように元になるクラスのことを「スーパークラス」、GeographicInfo3D型のように元になるクラスの定義を流用する側のクラスを「サブクラス」と呼びます。また、正式なJava用語ではありませんが、スーパークラスのことを「親クラス」や「基底クラス」あるいは「継承元クラス」、サブクラスのことを「子クラス」や「派生クラス」という場合もあります。

 クラスを継承すると、スーパークラスで定義したメンバ変数などをサブクラスで流用できるというメリットがあります。同じようなクラスを定義しなくてはならないが、微妙に異なるという場合は、まずすべてのクラスに共通する要素をスーパークラスで定義し、異なる部分だけを個別のクラスとして定義することで、プログラムを効率的に開発しましょう、というのがオブジェクト指向の基本的な考え方の1つとされています。

クラスを継承すると何が起きるのか

 クラスの継承は元のクラスを流用することです。ところが「Java入門」といった書籍や記事を読むと、次のような「例え話」が出てきます。

ありがちなクラス継承の説明図 クラスの継承とは、「電気製品」クラスを継承して「テレビ」クラスを作り、「テレビ」クラスを継承して「カラーテレビ」クラスを作るといったように、サブクラスがスーパークラスの性質を受け継ぐことである ありがちなクラス継承の説明図
クラスの継承とは、「電気製品」クラスを継承して「テレビ」クラスを作り、「テレビ」クラスを継承して「カラーテレビ」クラスを作るといったように、サブクラスがスーパークラスの性質を受け継ぐことである

 特にどの本というわけではないのですが、どうしてこういう例え話が出てきてしまうのか、私にはさっぱり分かりません。クラスの概念を哲学用語か何かのように説明するのはいいかげんやめにしてほしいものです。だいたい、テレビ型やカラーテレビ型クラスを作るJavaプログラマがどれだけいるというのでしょう。しかも、第2回「Javaの変数の本質を知る」で指摘した「変数は箱だ」説同様、この手の例え話を信じると、プログラムがどのようにメモリを使うのかという視点が抜け落ちてしまいます。

 そこで、クラスの継承がどのようにメモリを使うのか見てみましょう。まず、スーパークラスとなるGeographicInfo型のインスタンスは次のようにメモリを使います。

GeographicInfo型のインスタンスが使うメモリ領域 GeographicInfo型のインスタンスが使うメモリ領域

 GeographicInfo型にはdouble型のメンバ変数latitudeとlongitudeが含まれますので、全部で16bytesのメモリを消費します。一方、GeographicInfo 型のサブクラスであるGeographicInfo3D型は、GeographicInfo 型にdouble型のメンバ変数heightを追加定義したものです。GeographicInfo3D型のインスタンスは次のようにメモリを使います。

GeographicInfo3D型のインスタンスが使うメモリ領域 GeographicInfo3D型のインスタンスが使うメモリ領域

 GeographicInfo3D型の定義ではメンバ変数heightだけが宣言されていますが、GeographicInfo3D型はGeographicInfo 型のサブクラスです。従ってGeographicInfo3D型のインスタンスは、スーパークラスであるGeographicInfo型の16bytesにメンバ変数heightの分8bytesを加えて合計24bytesのメモリを消費します。

 いかがでしょうか。例え話を使った継承の説明では、こういう視点にたどり着くまでにずいぶん時間がかかります。そもそも、継承を示すキーワードである「extends」は「延長する」という意味です。クラスの継承とは「使用メモリ量の付け足しだ」と思えば理解はずいぶん早まるはずです。

クラス継承の理解を深める

 クラスを継承させるメリットは、クラス間の関係をプログラムの中に埋め込めることです。初めに説明したように、GeographicInfo型とGeographicInfo3D型をまったく独立して定義することもできますが、GeographicInfo型からGeographicInfo3D型を継承させることで、2つのクラス間の関係まで定義できるのです。このことを確かめるために、次のプログラムを実行してみてください。

   GeographicInfo3D型のインスタンスをGeographicInfo型として扱うプログラム
class GeographicInfo {
  double latitude;
  double longitude;
}

class GeographicInfo3D extends GeographicInfo {
  double height;
}

public class SubAsSuper {
  public static void main( String args[] ) {
      GeographicInfo g;
      GeographicInfo3D h  = new GeographicInfo3D();
      h.latitude    = 35.66;
      h.longitude   = 139.75;
      h.height      = 10.0;

      g = h;

      System.out.println(g.latitude);
      System.out.println(g.longitude);
  }
}

 このコードを実行すると以下のような結果になります。

C:\DOCUME~1\MYDOCU~1\MYJAVA~1>javac SubAsSuper.java

C:\DOCUME~1\MYDOCU~1\MYJAVA~1>java SubAsSuper
35.66
139.75


C:\DOCUME~1\MYDOCU~1\MYJAVA~1>

 初めに紹介したプログラムとよく似ていますが、決定的に異なるのは、スーパークラスであるGeographicInfo型の変数gを使ってGeographicInfo3D型として生成したインスタンスのメンバ変数latitudeとlongitudeの値を表示していることです。どういうことでしょうか。

 まず、「g = h;」という文によって、gにもhにも「new GeographicInfo3D()」という文で生成されたGeographicInfo3D型のインスタンスの先頭アドレスが格納されます。gはGeographicInfo型ですので、GeographicInfo3D型固有のメンバ変数であるheightにはアクセスできません。しかし、先頭から16bytesまでにあるlatitudeとlongitudeにはアクセスできるわけです。

 このことをクラス継承の例え話で理解するのはかなり厄介です。「カラーテレビ型はテレビ型の性質を受け継いでいるから、カラーテレビ型のインスタンスはテレビ型のインスタンスとしても扱える」といわれても、テレビの説明をしたいのかプログラミング言語の話をしたいのかさっぱり分かりません。

 クラスの継承の基本は「使用メモリ領域の付け足し」ですので、サブクラスのインスタンス化によって確保されたメモリ領域をスーパークラスのインスタンスとして扱えるのは当たり前と考えるべきなのです。

 さて、今回まで「変数はメモリをどのように使うのか」という視点でJavaを解説してきました。クラスについて説明をし尽くしていませんが、これ以上はメモリ領域の面だけでは解説できません。そこで次回からは制御文や関数の説明に入ります。


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。