オブジェクト指向プログラミング


 

継承

1 派生クラス

継承とは、あるクラスを土台にして、別のクラスを作成するための方法です。元になるクラスを「基底クラス」あるいは「スーパークラス」と呼び、それを継承する新しいクラスを「派生クラス」あるいは「サブクラス」と呼びます。

一つのクラスは、任意の個数の「派生クラス」を持つことが出来ますが、逆に、「基底クラス」は、多くのクラスで一つしか持てません。これを単一継承と呼び、クラスは継承されることで枝葉を広げていく木構造を成しています。そして、遡っていけば根っ子となる一つの「基底クラス」に行きつきます。ただし、C++ は、複数の「基底クラス」を継承できます。これを多重継承と呼びます。

クラスの木構造の頂点、ルートとなるクラスが Object クラスです。「基底クラス」を明示しないクラスの場合、自動的にこのクラスから派生されたことになります。「基底クラス」を明示した場合でも、そのクラスも Object クラスの「派生クラス」か、または Object クラスを継承したクラスの「派生クラス」のいずれかなので、最終的にはあらゆるクラスが Object クラスを「基底クラス」に持つことに変わりはありません。



「派生クラス」は、「基底クラス」の一種だと言えます。「乗り物」を継承して「自動車」を作り、「自動車」を継承して「バス」を作るようなもので、「バス」は「自動車」の一種であり、更に「乗り物」の一種です。

「派生クラス」は、「基底クラス」のメソッドやフィールドを全て継承して使えます。クラスの継承とはあるクラス(「基底クラス」)の機能を他のクラス(「派生クラス」)からでも使用できるということです。オブジェクトの中には非常に似た機能を持つものが多数あります。すべてのオブジェクトが、ただ1つのクラスから生成できればいいですが、クラスを新たに作成し、そこからオブジェクトを生成したほうがよい場合が多くあります。例えば、「バス」や「トラック」のオブジェクトを「自動車」クラスから生成するよりは、「自動車」クラスを継承し作成した「バス」や「トラック」クラスから生成したほうが効率が良いというわけです。


既存のクラスから派生したクラスを作成したい場合、クラス定義時に extends に続いて、基底クラス名を記述します。

public class <派生クラス名> extends <基底クラス名> { クラス定義 }

例えば「救急車」を考えて見ましょう。


「派生クラス」は、「基底クラス」の private でないメソッドやフィールドを全て継承して使えます。しかし、すべてを引き継いだだけで、「基底クラス」と何ら変わらないのならば、「派生クラス」を作る意味はありません。ですから、「派生クラス」では、「基底クラス」にない独自のメソッドを追加したり、「基底クラス」のメソッドの働きを変更したりします。つまり、「自動車」は、「種類」や「走る」といったような、自動車に共通する特性や機能を持ちますが、「救急車」は「自動車」の性質に、「サイレンを鳴らす」といったような、独自の性質を加えたものとなるのです。

「自動車」クラスを継承して「救急車」クラスを作成します。run メソッドは「自動車」クラスのものをそのまま利用します。しかし、そのままでは「自動車が走る。」となってしまうので、"自動車"という名前を持っている kind をコンストラクタで変更します。また、サイレンを鳴らしたいので、「救急車」クラスに独自の siren メソッドを作成してあげる必要があります。それでは Ambulance クラスとして作成してみましょう。

Car.java
public class Car { protected String kind = "自動車"; public Car() { } public Car(String kind) { // (2) this.kind = kind; } public void run() { //(4) System.out.println(kind + "が走る。"); } public void work() { System.out.println(""); } }
Ambulance.java
public class Ambulance extends Car { public Ambulance() { //(2) super("救急車"); } public void siren() { //(3) System.out.println("ピーポーピーポー"); } }

Ambulance クラスは、新たに siren というメソッドを持っています。それでは、この Ambulance クラスのインスタンスを生成し、siren と run メソッドを呼び出すクラスも作成してみましょう。

Main.java
public class Main { public static void main(String[] args) { Ambulance amb = new Ambulance(); //(1) amb.siren(); //(3) amb.run(); //(4) } }
  1. 〈宣言/定義〉
  2. Car を基底クラスに持つ派生クラス Ambulance を宣言します。
  3. 〈処理の流れ〉
  4. 派生クラス Ambulance のオブジェクト amb を宣言生成します。
  5. オブジェクト生成時、コンストラクタが実行されます。そこから基底クラスのコンストラクタを呼び出します。
  6. メソッド siren を呼び出しますAmbulance クラスの siren メソッドが実行されます。
  7. メソッド run を呼び出しますAmbulance 内にないので Car クラスの run メソッドが実行されます。

実行結果です。

ピーポーピーポー
救急車が走る。

2 オーバーライド

オーバーライドとは、「基底クラス」で定義されたメソッドを「派生クラス」で再定義することを言います。「基底クラス」のメソッドを変更することはできませんが、「派生クラス」に特化した機能を付与する必要がある場合などに使用します。

これに対して、オーバーロードとは、同一クラス内で、メソッド名が同一で引数の型、数、並び順が異なるメソッドを複数定義することを言います。


オーバーライドを定義する際には以下の規定があります。

(ただし、データの型やアクセスレベルのない言語ではこのような規定はありません。)


java の場合は、さらに次の規定があります。

  • オーバーライドされる側のインスタンスメソッドに final が指定されている場合、そのメソッドは「派生クラス」でオーバーライドできません。

例えば「消防車」を考えて見ましょう。「自動車」クラスを継承して「消防車」クラスを作成します。work メソッドを呼び出しても何も表示されないので、独自の性質として「火事を消す」という work メソッドを作成してあげる必要があります。それでは FireEngine クラスとして作成してみましょう。

Car.java
public class Car { protected String kind = "自動車"; public Car() { } public Car(String kind) { //(2) this.kind = kind; } public void run() { //(3) System.out.println(kind + "が走る。"); } public void work() { System.out.println(""); } }
FireEngine.java
public class FireEngine extends Car { public FireEngine() { //(2) super("消防車"); } public void work() { //(4) System.out.println("火事を消す。"); } }

FireEngine クラスは、オーバーライドした work というメソッドを持っています。それでは、この FireEngine クラスのインスタンスを生成し、run メソッドと work メソッドを呼び出すクラスも作成してみましょう。

Main.java
public class Main { public static void main(String[] args) { FireEngine fir = new FireEngine(); //(1) fir.run(); //(3) fir.work(); //(4) } }
  1. 〈宣言/定義〉
  2. Car を基底クラスに持つ派生クラス FireEngine を宣言します。
  3. 〈処理の流れ〉
  4. 派生クラス FireEngine のオブジェクト変数 fir を宣言生成します。
  5. オブジェクト生成時、コンストラクタが実行されます。そこから基底クラスのコンストラクタ呼び出します。
  6. メソッド run を呼び出しますクラス FireEngine 内にないので基底クラス Car の run が実行されます。
  7. メソッド work を呼び出します基底クラス Car の work ではなく、クラス FireEngine 内の work が実行されます。

実行結果です。

消防車が走る。
火事を消す。