継承

継承とは、クラスの要素(メンバ変数やメソッド)を自身に作成するのではなく、別のクラスから取り込む事ができる仕組みです。


継承の機能

例として、「Aクラス」を継承したものを「Bクラス」とすると、「Bクラス」は次の図のような特性を持つことになります。

継承のイメージ

※但し、「Aクラス」の要素で「アクセス装飾子」が「private」のもの、および「コンストラクタ」は引き継ぐ事が出来ません。


ちなみにクラスの継承関係をUMLで表記すると次のようになります。

継承のUML図

このとき、「Bクラス」からみて「Aクラス」のことを「親クラス」または「スーパークラス」と呼び、反対に「Aクラス」からみて「Bクラス」の事を「子クラス」または「サブクラス」と呼びます。


継承の書式

継承の書式は次の通りです。

class 子クラス名 extends 親クラス名{
}

※C++などでは「親クラス」を複数指定することが出来ますが、Javaでは「親クラス」は1つしか指定する事が出来ません(単一継承)。


「子クラス」から「親クラス」へのアクセス

「子クラス」から「親クラス」の「メンバ変数」や「メソッド」を使用するには次のように記述します。

class 子クラス名 extends 親クラス名{
    super.親クラスのメンバ変数;
    super.親クラスのメソッド;
}

「super.」は省略可。
但し、子クラスに親クラスと同じ名前のメンバ変数やメソッドがある場合は、明示的に「super.」を付加しないと自クラス(子クラス)のものが使用されます。


「親クラス」から「子クラス」へのアクセスは用意されていません。

「子クラス」はその宣言時に「親クラス」を指定しているので「親クラス」へアクセス出来ます。しかし、「親クラス」はどのクラスに継承されるか解らないのでアクセスしようがありません。当然、アクセス方法は用意されていません。

ちなみに上記クラス図で「子クラス」から「親クラス」へ「△」の記号で指していますが、これはこういった意味からきています。 つまり「子クラス」は「親クラス」を示せるが、「親クラス」は「子クラス」を示す事ができないということです。


コンストラクタの呼び出し規則

「親クラス」と「子クラス」のコンストラクタは次の規則で呼び出されます。

  1. 「子クラス」のインスタンス生成時、まず「親クラス」のコンストラクタが呼ばれ、その後「子クラス」のコンストラクタが呼ばれる。
  2. 「親クラス」の呼ばれるコンストラクタは、引数がない「コンストラクタ名()」が呼び出される。
    もし、「親クラス」に引数があるコンストラクタしかない場合は、「親クラス」に引数なしのコンストラクタを作成して、そこから引数有りのコンストラクタを呼び出すようにするか、または「子クラス」から「super(引数リスト);」で明示的に呼び出す必要があります。

実際に継承の動作を次のサンプルで確認します。


・サンプルソース(Sample0801.java)

class Parent0801 {
    public String ps1="親クラスのメンバ変数が参照されました。";
    public Parent0801(){
        System.out.println("親クラスのコンストラクタ(引数なし)が呼ばれました。");        
    }
    public void pm() {
        System.out.println("親クラスのメソッドが呼ばれました。");
    }
}

class Child0801 extends Parent0801 { // Parentクラス(親クラス)を継承。
    public String cs1="子クラスのメンバ変数が参照されました。";
    public Child0801(){
        System.out.println("自クラスのコンストラクタ(引数なし)が呼ばれました。");
    }
    public void cm() {
        System.out.println("子クラスのメソッドが呼ばれました。");
    }
}

public class Sample0801 {
    public static void main(String[] args) {
        Child0801 child = new Child0801(); // 子クラスのインスタンスを生成。
        System.out.println(child.ps1); // 親クラスのメンバ変数を参照。
        System.out.println(child.cs1); // 子クラスのメンバ変数を参照。
        child.pm(); // 親クラスのメソッドの呼び出し。
        child.cm(); // 子クラスのメソッドの呼び出し。
    }
}

・実行結果

C:\dev\java>java Sample0801 [Enter]
親クラスのコンストラクタ(引数なし)が呼ばれました。  ← 子クラスのインスタンス生成時に親クラスのコンストラクタが先に呼ばれる。
自クラスのコンストラクタ(引数なし)が呼ばれました。
親クラスのメンバ変数が参照されました。  ← 子クラスから親クラスのメンバ変数を参照できている。
子クラスのメンバ変数が参照されました。
親クラスのメソッドが呼ばれました。  ← 子クラスから親クラスのメソッドを呼び出せている。
子クラスのメソッドが呼ばれました。
        

▲PageTop

オーバーライド

「親クラス」に存在するメソッドと同じシグニチャのメソッドを作成する事が出来ます。

これを「オーバーライド」といい、ちょうど「親クラス」のメソッドが「子クラス」で書き換えられたような感じになります。オーバーライドされた「親クラス」のメソッドを呼び出したいときは「super.」を付加し「親クラス」を明示することで呼び出し可能です。


※よく似た呼び名で「オーバーロード」がありますが別物ですので注意してください。


・サンプルソース(Sample0802.java)

class Parent0802 {
    public void same(){
        System.out.println("親クラスのsameメソッドが呼ばれました。");
    }
}

class Child0802 extends Parent0802 {
    public void same(){
        System.out.println("自クラスのsameメソッドが呼ばれました。");
    }
    public void calltest() {
        same();
        super.same();
    }
}

public class Sample0802 {
    public static void main(String[] args) {
        Child0802 child = new Child0802();
        child.calltest();
    }
}

・実行結果

C:\dev\java>java Sample0802 [Enter]
自クラスのsameメソッドが呼ばれました。
親クラスのsameメソッドが呼ばれました。
        

修飾子の表示レベル

オーバーライドを行うとき、アクセス修飾子の制限レベルを上げることはできません。

アクセス修飾子の制限レベルの強度は次のようになります。

制限が弱い←  public < protected < デフォルト(省略時) < private  →制限が強い

※アクセス修飾子の詳細はこちらの「アクセス修飾子」を参照してください。


従って、例えば親クラスのアクセス修飾子が「protected」のものをオーバーライド時に、修飾子を省略したり「private」に変更することはできません。

▲PageTop

継承とコンポジション

別のクラスの要素を利用できる方法として、「継承」の他に「コンポジション」(集約)があります。

「コンポジション」はメンバ変数として別のクラスを取り込んだ形です。


クラス設計において、「継承」と「コンポジション」の使い分けは次の関係で判断します。


1.「継承」の関係

「継承」は「A is a B」のとき、つまり「AはBである」といえる場合に使用します。

例えば、「人間クラス」と「プログラマークラス」があった場合、「プログラマーは人間である」といえるので「継承」関係にすることが出来ます。これを「Is-a」関係と呼びます。


2.「コンポジション」の関係

「コンポジション」は「A has a B」のとき、つまり「AはBをもっている」といえる場合に使用します。

例えば、「プログラマークラス」と「パソコンクラス」があった場合、「プログラマーはパソコンをもっている」といえるので「コンポジション」関係にすることが出来ます。これを「has-a」関係と呼びます。


・サンプルソース(Sample0803.java)

class Human0803 {
  public void think(){
    System.out.println("考えています・・・。");
  }
}

class Computer0803 {
  public void process(){
    System.out.println("処理をしています・・・。");
  }
}

class Programmer803 extends Human0803 {
  public Computer0803 comp = new Computer0803();
}

public class Sample0803 {
  public static void main(String[] args) {
    Programmer803 pg = new Programmer803();
    pg.think();
    pg.comp.process();
  }
}

・実行結果

C:\dev\java>java Sample0803 [Enter]
考えています・・・。
処理をしています・・・。
        

▲PageTop