クラスの継承(Inheritance)

サブクラス(Subclass)

継承は,オブジェクト指向プログラミング(OOP)の基本の1つです.すでにあるクラスを用いて新しいクラスを作成したい場合があります.新しいクラスを最初から作成するのではなく,モデルとなるクラスを土台にして新しいクラスを記述できれば便利です.Javaでは,extendsキーワードを用いて継承を行います.継承の元になるクラスをスーパークラス(基本クラス)といい,新しいクラスをサブクラス(派生クラス)といいます.

継承(Inheritance)

is-a
の関係と言われます.なぜなら,"is"で定義された全てのオブジェクトは派生されたクラスのオブジェクトとなるからです.

クラスXからクラスYを派生する記述法は,

class Y extends X
{
// 派生クラスに追加するメンバの宣言
}
です.ここで,Xはスーパークラス,Yはサブクラス.

例題 7..1   Personクラス
\framebox{
\begin{minipage}{13.65cm}
{\rm 名前,国籍を持ち,名前と国籍を表示できる機能を持った人物クラスPersonを作成しなさい.}
\end{minipage}}

解答 Personクラスにメインメソッドを含んでいる場合.

  1. Personクラスの宣言を行なう.Personクラスは,名前,国籍を持つので,フィールド変数は,
    private String name,nationality;
    とする.
  2. コンストラクタの定義を行なう.
    public Person(String name, String nationality)
    {
    this.name = name;
    this.nationality = nationality;
    }
    this.nameはフィールド変数nameを指します.したがって,this.name = name;でコンストラクタが受け取った変数nameを用いて,フィールド変数の初期化を行います.
  3. mainメソッドの定義を行なう.
  4. Person型のオブジェクトを作り利用する.

class Person
{
  private String name, nationality;
  public Person(String name, String nationality)
  {
    this.name = name;
    this.nationality = nationality;
  }
  public static void main(String[] args)
  {
    Person p = new Person("横田","日本");
    System.out.println("名前は" + p.name);
    System.out.println("国籍は" + p.nationality);
  }
}

実行結果

\begin{figure}\centering
\includegraphics[width=7.8cm]{JAVAFIG/Person.eps}
\end{figure}

解答 Personクラスにメインメソッドを含んでいない場合.

  1. Personクラスの宣言を行なう.
    Personクラスは別のクラスで用いられるので,フィールド変数は,
    protected String name,nationality;
    とします.
  2. コンストラクタの定義を行なう.
    public Person(String name, String nationality)
    {
    this.name = name;
    this.nationality = nationality;
    }
  3. 別のクラスを用意してメインメソッドの定義を行なう.この場合,ファイル名はメインメソッドのあるクラス名とする.
  4. Person型のオブジェクトを作り利用する.

class Person
{
  private String name, nationality;
  public Person(String name, String nationality)
  {
    this.name = name;
    this.nationality = nationality;
  }
}
public class PersonIni
{
  public static void main(String[] args)
  {
    Person p = new Person("横田","日本");
    System.out.println("名前は" + p.name);
    System.out.println("国籍は" + p.nationality);
  }
}

実行結果

\begin{figure}\centering
\includegraphics[width=7.8cm]{JAVAFIG/PersonIni.eps}
\end{figure}

スーパーコンストラクタの呼び出し

例題 7..2   クラスの拡張
\framebox{
\begin{minipage}{13.65cm}
{\rm 学生番号,成績評価をメンバ変数とするStudentクラスをPersonクラスのサブクラスとして作成せよ.}
\end{minipage}}

class Person
{
  protected String name, nationality;
  public Person(String name, String nationality)
  {
    this.name = name;
    this.nationality = nationality;
  }
}
class Student extends Person
{
  protected String id;
  protected double gpa;
  public Student(String id, double gpa)
  {
    super("横田","日本");// スーパーコンストラクタの呼び出し
    this.id = id;
    this.gpa = gpa;
  }
}
public class StudentExtPerson
{
  public static void main(String[] args)
  {
    Student st = new Student("600813902",3.85);
    System.out.println("名前は" + st.name);
    System.out.println("国籍は" + st.nationality);
    System.out.println("学生番号は" + st.id);
    System.out.println("gpaは" + st.gpa);
  }
}

実行結果

\begin{figure}\centering
\includegraphics[width=7.8cm]{JAVAFIG/StudentExtPerson.eps}
\end{figure}

メンバへのアクセス

privateメンバはクラスの中からだけアクセス可能です.
protectedメンバはクラス中からとその派生クラスからもアクセス可能です.
publicメンバはファイル内のどこからでもアクセス可能です.

一般に,protectedはそのクラスの派生クラスを定義する可能性があるときにprivateの代わりに用います. 派生クラスはその基本クラスのpublicおよびprotectedの全てのメンバを継承します.つまり,派生クラスから見ると,基本クラス(スーパークラス)のpublicおよびprotectedのメンバは,あたかも派生クラスで宣言されたようになります.例えば,クラスXと派生クラスYが

class X
{
    public int a;
    protected int b;
    private int c;
}
class Y extends X
{
    public int d;
}
で定義され,オブジェクトxとyが
    X x = new X();
    Y y = new Y();
と宣言されたとします.これを図で表すと次のようになります.

\begin{figure}\centering
\includegraphics[width=5.8cm]{CPPPIC/fig12-1.eps}
\end{figure}

クラスXのpublicメンバaはYのpublicメンバとして継承され,クラスXのprotectedメンバbはYのprotectedメンバとして継承されますが,クラスXのprivateメンバcはクラスYに継承されません.

継承されたメンバのオーバーライド(Overriding Inherited Members)

Xが基本クラス(スーパークラス)で,YがXのサブクラス(派生クラス)のとき,YのオブジェクトはXの全てのpublicまたはprotectedメンバ変数およびメンバ関数を継承します.例えば,上の例題のPersonクラスのフィールド変数nameなどです.

ところが,ときには継承されたメンバを派生クラスに合わせた形に定義したいときがあります.例えば,nameがPersonクラスのフィールド変数で,StudentがPersonクラスの派生クラスのとき,nameというメンバ変数をStudentクラスに新たに定義することができます.このとき,Studentで定義されたフィールド変数nameはPersonクラスのフィールド変数nameに対し 優位(dominate)であるといいます. ここで,クラスStudentのオブジェクトsによるnameの参照s.nameはPersonクラスで定義されたnameではなく,Studentで定義されたフィールド変数nameをアクセスします.Personクラスのフィールド変数nameをアクセスするときにはsuperキーワードを用いて,super.nameとします.つまり,Studentクラスオブジェクトは基本クラス(スーパークラス)とサブクラスの両方のメンバにアクセスできます.

例題 7..3   フィールドのオーバーライド
\framebox{
\begin{minipage}{13.65cm}
{\rm PersonクラスからStudentクラスを派生させ,PersonクラスのオブジェクトpとStudentクラスのオブジェクトsを用意し,両方のクラスのメンバ変数nameにアクセスするプログラムを作成しなさい.}
\end{minipage}}

解答 StudentクラスオブジェクトsがStudentクラスのメンバ変数nameにアクセスするには,s.nameでよい.また,Personクラスのメンバ変数にアクセスするには,super.nameのように,superを用いる.

class Person
{
  protected String name, nationality;
  public Person(String name, String nationality)
  {
    this.name = name;
    this.nationality = nationality;
  }
}
class Student extends Person
{
  protected String id;
  protected double gpa;
  protected String name;
  protected String nameP;
  public Student(String name, String id, double gpa)
  {
    super("横田","日本");
    nameP = super.name;
    this.name = name;
    this.id = id;
    this.gpa = gpa;
  }
}
public class FieldOverride
{
  public static void main(String[] args)
  {
    Student st = new Student("Yokota","600813902",3.85);
    System.out.println("名前は" + st.nameP);
    System.out.println("ローマ字で書くと" + st.name);
  }
}

実行結果

\begin{figure}\centering
\includegraphics[width=7.8cm]{JAVAFIG/FieldOverride.eps}
\end{figure}

メソッドに対しても同じことがいえます.printName()メソッドがPersonクラスで定義され,さらにStudentクラスでも定義されたならば,Studentクラスのオブジェクトsによる呼び出しs.printName()はStudentで定義されたprintName()メソッドを呼び出します.Personクラスで定義されたprintName()メソッドを呼び出すには,キーワードsuperを用いて,super.printName()とします.このとき, メソッドs.printName()はprintName()メソッドをオーバーライド(override)するといいます.

練習問題 7..1  
printName()メソッドを含むPersonクラスとその派生クラスStudentクラスを用い,Studentクラスオブジェクトsを用意し,s.printName()とsuper.printName()を比較しなさい.

派生クラスから基本クラスのメンバへのアクセス

すでに説明しましたように,privateとprotectedの違いは,派生クラスからprotectedメンバはアクセスできますがprivateメンバはできないところです.では,いつメンバをprivateにしたらよいのでしょうか.その答えは,principle of information hiding(情報隠蔽原則)で説明されます.つまり,最初はアクセスを制限し,後で変更します.もし,将来,メンバ変数の変更を考えているのなら,privateで宣言しておくことにより,派生クラスでの変更を必要のないものにします.派生クラスはprivateデータメンバと独立です.

例えば,Personクラスのオブジェクトである人物が大卒かどうか知る必要があるとします.このとき,フィールド変数collegeをprotectedで追加することができますが,後で,もっと詳しい学歴の情報を付け加えるかもしれないので,この段階ではprivateメンバ変数にし,派生クラスからのアクセスを制限するほうがよいでしょう.

確認問題 7..1  

1. 合成と継承の違いは何か

2. protecedとprivateメンバの違いは何か

3. 仮想関数とは何か

4. 抽象基本クラスとは何か

5. 多相とは何か

6. 具象導出クラスとは何か

7. 静的結合と動的結合の違いは何か

演習問題 7..1  

1. 次の定義はどこが間違っているか
class X
{
protected:
int a;
};
class Y : public X
{  public:
void set(X x, int c) {x.a = c;}
};

2. 次のクラス階層を実装せよ.

\begin{figure}% latex2html id marker 1430
\centering
\includegraphics[width=10.7cm]{CPPPIC/enshu12-2.eps}
\end{figure}