継承(inheritance)

オブジェクト指向の目標の一つに,ソフトウェアの再利用ということがある. プログラムを作る際に,すべてを1から作るのは大変である.人が作った,似 たようなことをするプログラムをちょっと変更して使うという手はあるが, などの問題がある.

そのため,人が作ったかもしれないあるクラスの定義を拡張して,新たなク ラスを作成する手段が用意されている.これをクラスの継承(inheritance)と 呼び,元のクラスを親クラス(又はスーパークラス super class),新たに作成 されたクラスを子クラス(又はサブクラス sub class)と呼ぶ.


サブクラスの宣言

サブクラスの宣言は以下のようにおこなう

class 子クラス名 extends 親クラス名 {
 クラス宣言の本体
}
たとえばこれまでの例で,Point2Dを親クラスとして,nameというString型の データフィールドも持つPointNameというクラスを作ってみよう.
class Point2D {
  public double x,y;
  public Point2D(double xx,double yy){
    x=xx; y=yy;
  }
  public double length(){
    return Math.sqrt(x*x+y*y);
  }
  public double distance2D(Point2D point2){
    double dx=(x-point2.x),dy=(y-point2.y);
    return Math.sqrt(dx*dx+dy*dy);
  }
}

  // Point2Dクラスのサブクラスとして PointNameクラスを作成するので、
  // 「extends Point2D」をつける。
class PointName extends Point2D {
    // Point2Dクラスには含まれないインスタンス変数 name を宣言する。
  public String name;
    // PointNameクラスのコンストラクタ
  public PointName(double xx,double yy,String n){
      // 親クラスであるPoint2Dクラスのコンストラクタを呼ぶ。
      // (x=xx; y=yy;)に当たる部分が実行される。
    super(xx,yy);
    name=n;
  }
}

class ObjectTest6 {
  public static void main(String[] args){
    PointName p1,p2,p3;
      // PointNameクラスのコンストラクタを呼び出しオブジェクトを作成する。
    p1=new PointName(0.0,0.0,"Point1");
    p2=new PointName(1.0,1.0,"Point2");
    p3=new PointName(0.0,1.0,"Point3");
      // Point2Dクラスにはなかったインスタンス変数 name を参照でき、
      // Point2Dクラスで定義したlengthメソッドを新たな定義なしに使える。
    System.out.println("length of "+p1.name+" = " +p1.length());
    System.out.println("length of "+p2.name+" = " +p2.length());
    System.out.println("length of "+p3.name+" = " +p3.length());
    System.out.println("distance between "+p1.name+" and " +p2.name+" = "+p1.distance2D(p2));
    System.out.println("distance between "+p2.name+" and " +p3.name+" = "+p2.distance2D(p3));
    System.out.println("distance between "+p3.name+" and " +p1.name+" = "+p3.distance2D(p1));
  }
}
これまでの Point2Dに加えて,今後の説明のため,原点との距離を記述する lengthというメソッドも加えている.

このように PointNameの定義の中では,データフィールド x, yやメソッド distanceの宣言をおこなっていないが,親クラスPoint2Dの性質を受け継いで, 使うことができる.コンストラクタ PointNameの中で superとやって呼び出し ているのは親クラスのコンストラクタである.


メソッドのオーバーライド

Point2Dを親クラスとして,3次元のPointであるPoint3Dを定義することを考え てみよう.今度は,lengthの定義を変更してみる.
class Point2D {
  public double x,y;
  public Point2D(double xx,double yy){
    x=xx; y=yy;
  }
  public double length(){
    return Math.sqrt(x*x+y*y);
  }
  public double distance2d(Point2D point2){
    double dx=(x-point2.x),dy=(y-point2.y);
    return Math.sqrt(dx*dx+dy*dy);
  }
}

  // Point2Dクラスのサブクラスとして、Point3Dクラスを定義する。 
class Point3D extends Point2D {
    // Point2Dクラスにはなかったインスタンス変数 zが加わる。
  public double z;
    // Point3Dクラスのコンストラクタの定義
  public Point3D(double xx,double yy,double zz){
      // 親クラスである Point2Dクラスのコンストラクタを呼び出す。
    super(xx,yy);
    z=zz;
  }
    // 親クラスである Point2Dで定義した length はPoint3Dでは使わずに、
    // ここで再定義する。
  public double length(){
    return Math.sqrt(x*x+y*y+z*z);
  }
}

class ObjectTest7 {
  public static void main(String[] args){
    Point3D p1,p2,p3;
    p1=new Point3D(0.0,0.0,1.0);
    p2=new Point3D(1.0,1.0,1.0);
    p3=new Point3D(0.0,1.0,0.0);
      // p1はPoint3Dクラスのインスタンスなので、
      // lengthメソッドは Point3Dクラスのメソッドが呼ばれる。
    System.out.println("length of p1 = "+p1.length());
    System.out.println("length of p2 = "+p2.length());
    System.out.println("length of p3 = "+p3.length());
  }
}
上のように,親クラスで定義されているメソッドを同じ名前(及び同じ引数の 型)で再定義することをメソッドのオーバライド(override)と呼ぶ.

親クラスの型の変数に子クラスの型のオブジェクトを代入することはできる. その場合も,動的メソッドの呼び出しをした場合は子クラスでオーバーライド されたメソッドのプログラムが実行される.

この例では親クラス Point2D,子クラス Point3Dなので,

 Point2D p1; // 親クラスの変数
 p1 = new Point3D(1.0,1.0,1.0); // 子クラス(Point3D)のオブジェクトを代入
とでき,その後で,
 double len=p1.length(); 
のように動的メソッド length を呼び出すと,変数の型である Point2Dで定義 されたlengthではなく,インスタンスの型である Point3Dで定義された lengthが呼ばれる.

子クラスの型の変数に親クラスのオブジェクトを代入する際には,キャスト演 算子を使って,「(子クラス名)式」のように使う.ただし,データフィールド をアクセスしようとしたり,メソッドを呼び出そうとすると,実行時にエラー が発生する.

これでは,意味がないようだが,子クラスのオブジェクトを親クラスの変数に 代入したあとで,また子クラスの変数に代入するという特殊な用途で役に立つ.
それでは,PointNameとPoint3Dの両方の性質を受け継いだクラスは作れるの だろうか? これは,オブジェクト指向言語では多重継承(multiple inheritance)と呼ばれるもので,効率的な実装が難しい上に,プログラムの設 計においては,あまりメリットがないと言われている.C++ は途中のバージョ ンから多重継承を言語仕様に入れたが,これは失敗だったという専らの評判で ある.

Java言語では多重継承に変わるものとして,インタフェースの宣言とその実 装という仕組みを用意しているが,これに関しては後回しにする.

すべてのクラスは Object というクラスのサブクラスになっている.これは, Vectorなどを使う際には有用であるが,普通は特に気にしなくても良い.
自分の書いたクラスを継承する別のクラスを作るということは,初級プログ ラミングにおいては,あまり出てこないだろう.継承して再利用することので きるクラスを適切に設計するというのは,ある程度熟練が必要な作業だからで ある.一方,Java言語の 標準API 中のクラスの多くは,継承して利用するように設計されている.しかし, String クラス のように,
public final class クラス名
のように,finalクラスとして定義されている.このようなクラスからは,継 承してサブクラスを作ることはできない.


課題1

Point2Dクラスのサブクラスとして, length(), distance3d(Point3DName), distance2d(Point2D)というメソッド, x, y, z, nameというインスタンス変数を持つPoint3DNameというサブクラスを作って, 次のプログラムを完成させなさい.

class Point2D {
  public double x,y;
  public Point2D(double xx,double yy){
    x=xx; y=yy;
  }
  public double length(){
    return Math.sqrt(x*x+y*y);
  }
  public double distance2d(Point2D point2){
    double dx=(x-point2.x),dy=(y-point2.y);
    return Math.sqrt(dx*dx+dy*dy);
  }
}

class Point3DName extends Point2D {
// この部分を埋めなさい.
}

class ObjectTest8 {
  public static void main(String[] args){
    Point3DName p1,p2,p3;
    p1=new Point3DName(0.0,0.0,1.0,"Point1");
    p2=new Point3DName(1.0,1.0,1.0,"Point2");
    p3=new Point3DName(0.0,1.0,0.0,"Point3");
    System.out.println("length of "+p1.name+" = "+p1.length());
    System.out.println("length of "+p2.name+" = "+p2.length());
    System.out.println("length of "+p3.name+" = "+p3.length());
    System.out.println("2D distance between "+p1.name+" and " +p2.name+" = "+p1.distance2d(p2));
    System.out.println("2D distance between "+p2.name+" and " +p3.name+" = "+p2.distance2d(p3));
    System.out.println("2D distance between "+p3.name+" and " +p1.name+" = "+p3.distance2d(p1));
    System.out.println("3D distance between "+p1.name+" and " +p2.name+" = "+p1.distance3d(p2));
    System.out.println("3D distance between "+p2.name+" and " +p3.name+" = "+p2.distance3d(p3));
    System.out.println("3D distance between "+p3.name+" and " +p1.name+" = "+p3.distance3d(p1));
  }
}

課題をこなしたら, lectures.g99.cp1-ktanaka-W-Wed-5 のニュースグループに投稿すること.
ktanaka@XXX.ecc.u-tokyo.ac.jp

次へ進む