8 章: 継承

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

継承の機能

例として, [A クラス] を継承したものを [B クラス] とすると, [B クラス] は次のような特性を持つことになる.

_images/java-inheritance012.gif

ただし:

[A クラス] の要素で [アクセス修飾子] が [private] のもの, および [コンストラクタ] は引き継ぐことができない.

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

_images/java-inheritance022.gif

このとき, [B クラス] からみて [A クラス] のことを [親クラス] または [スーパークラス] と呼び, 反対に [A クラス] からみて [B クラス] のことを [子クラス] または [サブクラス] と呼ぶ.

継承の書式

継承の書式は次の通りである:

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

注意:

C++ などでは [親クラス] を複数指定することができるが, Java では [親クラス] は1つしか指定することができない. (単一継承).

[子クラス] から [親クラス] へのアクセス

[子クラス] から [親クラス] の [メンバ変数] や [メソッド] を使用するには次のように記述する:

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

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

[親クラス] から [子クラス] へのアクセスは用意されていない.

[子クラス] はその宣言時に [親クラス] を指定しているので [親クラス] へアクセスできる. しかし, [親クラス] はどのクラスに継承されるかわからないので, アクセスしようがない. 当然, アクセス方法は用意されていない.

ちなみに, 上記のクラス図で [子クラス] から [親クラス] へ [ △ ] の記号で指しているが, これはこういった意味からきている.

つまり:

[子クラス] は [親クラス] を示せるが,
[親クラス] は [子クラス] を示すことができない.

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

[親クラス] と [子クラス] のコンストラクタは次の規則で呼び出される.

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

実際に継承の動作を次のサンプルで確認する.

Sample0801.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Parent0801{
    public String ps1 = "親クラスのメンバ変数が参照された."; // もし public を private に変われば, 子クラスからアクセスできなくなる.
    
    public Parent0801(){
	System.out.println("親クラスのコンストラクタ (引数なし) が呼ばれた.");
    }

    public void pm(){
	System.out.println("親クラスのメソッドが呼ばれた.");
    }
}

class Child0801 extends Parent0801{ // Parent0801 クラス (親クラス) を継承
    public String cs1 = "子クラスのメンバ変数が参照された."; // public を private に変われば, child.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(); // 子クラスのメソッドの呼び出し
    }
}

上記のプログラムの実行結果:

[wtopia 8]$ java Sample0801
親クラスのコンストラクタ (引数なし) が呼ばれた.
子クラスのコンストラクタ (引数なし) が呼ばれた.
親クラスのメンバ変数が参照された.
子クラスのメンバ変数が参照された.
親クラスのメソッドが呼ばれた.
子クラスのメソッドが呼ばれた.

オーバーライド

[親クラス] に存在するメソッドと同じシグニチャのメソッドを作成することができる.

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

Sample0802.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
    }
}

上記のプログラムの実行結果:

[wtopia 8]$ java Sample0802
子クラスの same メソッドが呼ばれた.
親クラスの same メソッドが呼ばれた.

修飾子の表示レベル

オーバーライドを行うとき, アクセス修飾子の制限レベルをあげることはできない.

アクセス修飾子の制限レベルの強度は次のようになる:

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

従って, 例えば親クラスのアクセス修飾子が [protected] のものをオーバーライド時に, 修飾子を省略したり, [private] に変更することはできない.

継承とコンポジション

別のクラスの要素を利用できる方法として, [継承] の他に [コンポジション (集約)] がある. [コンポジション] はメンバ変数として別のクラスを取り込んだ形.

クラス設計において, [継承] と [コンポジション] の使い分けは次の関係で判断する.

  1. [継承] の関係:
[継承] は [A is a B] のとき, つまり [A は B である] といえる場合に使用する. 例えば, [人間クラス] と [プログラマークラス] があった場合, [プログラマーは人間である] といえるので [継承] 関係にすることができる. これを [is-a] 関係と呼ぶ.
  1. [コンポジション] の関係:
[コンポジション] は [A has a B] のとき, つまり [A は B をもっている] といえる 場合に使用する. 例えば, [プログラマークラス] と [パソコンクラス] があった場合, [プログラマーはパソコンをもっている] といえるので, [コンポジション] 関係にすることができる. これを [has-a] 関係と呼ぶ.

Sample0803.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Human0803{
    public void think(){
	System.out.println("考えている...");
    }
}

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

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

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

上記のプログラムの実行結果:

[wtopia 8]$ java Sample0803
考えている...
処理をしている...