継承とは
この節の要点
- 既にあるクラスを元に別のバリエーションのクラスを作る方法を継承と呼ぶ。
\({\Huge ``}\)... we develop a method by which more elaborate concepts can be constructed on the basis of simpler ones. This will establish potential hierarchies of concepts, with complex concepts subordinate to the more simple ones in terms of which they are defined. \({\Huge,\hspace{-0.4em},}\)
----- Ole-Johan Dahl, C. A. R. Hoare, III Hierachical Program Structures, p.202[7]
\({\Huge``}\)単純な概念をもとにして、もっとこみ入った概念を構成する方法を開発することにしよう。これにより、複雑な概念はそれを定義する単純な概念に従属する、という意味で、概念に階層をつけることになる。\({\Huge,\hspace{-0.4em},}\)
------ オーレ・ヨハン・ダール, トニー・ホーア, III 階層的プログラム構造. p.226[7]
構造化プログラミングにおいて、ダイクストラが主張するプログラムの階層化は、(当時のプログラミング言語の仕様上)プログラム構造のテキスト的・順序的・文脈的な構造化で実現されていたこと対し、オブジェクト指向プログラミングにおいては、テキスト(モジュール)の階層化ではなく概念(クラス)の階層化をします。この概念の階層化によって、モジュールの独立性を高めることを目指すわけです。
オブジェクト指向プログラミングにおける概念の階層化する方法として継承(inheritance) 1 があります。具体的には、あるクラス(上位の概念)からサブクラス(従属する下位の概念)を構成することを継承と呼びます。ここで、継承元クラスをスーパークラスあるいは基底クラスとも呼びます。
下位の概念は、上位のより一般性を持った概念と振る舞いを共有することは容易に想像できます。サブクラスでは、スーパークラスのメソッドやフィールドを再定義することなく利用することができます。よって、下位の概念を新たに作成する際には、既存の概念の定義を流用する形で拡張機能のみを追加定義することになり、拡張性が高まることになります。
図28に継承の例として、図27のHelloクラスを継承したIntroductionクラスを示します。
public class Hello {
public Hello() {
System.out.println("こんにちは");
}
}
public class Introduction extends Hello {
public Introduction(String name) {
super(); // スーパークラスのコンストラクタ呼び出し
System.out.println("私の名前は" + name + "です");
}
}
継承を行う際には、図29に示すように、クラス名(ここではIntroductionクラス)の宣言直後のセミコロンに続いて、継承するクラスであるスーパークラス名(ここではHelloクラス)を記述することで、クラスの継承関係(IntroductionクラスはHelloクラスを継承する)を定義しなければなりません。
public class Introduction extends Hello {

サブクラスのインスタンス生成にあたって初期化を行う上で、同時に継承元の基底クラス(スーパークラス)を表現するためのデータの初期化も行わなければならない(そうでなければ基底クラスの振る舞いに対する用意ができない)。
図31に示すように、コンストラクタの内で、superキーワードをメソッドのように呼び出しています。superキーワードは、スーパークラスそのものを表していて、コンストラクタとして呼び出したり、あるいは、thisキーワードと同様にスーパークラスのフィールド・メソッド呼び出しとしても利用できます。
図30にスーパークラスのコンストラクタ呼び出しのイメージを示します。サブクラスのコンストラクタを呼び出した際には、サブクラスの初期化処理を実行する前に、superキーワードを使ってスーパークラスのコンストラクタを呼び出すことになります。
public Introduction(String name) {
super(); // スーパークラスのコンストラクタ呼び出し
また、他の継承の例として、図32のUserクラスを継承したStudentクラスを示します。Studentクラスは学生データを表す概念であり、ユーザデータの概念の下位の概念に相当する。ここでは学籍番号の取得のためのメソッドであるgetIDメソッドが追加されている。
public class Student extends User {
private String studentID;
private int entryYear;
private int no;
public Student(int no, int entryYear, String familyName, String firstName) {
super(familyName, firstName);
this.no = no;
this.entryYear = entryYear;
this.studentID = "u" + familyName.charAt(0) + firstName.charAt(0) + (entryYear - 100 * (entryYear / 100)) + String.format("%02d", no);
}
public String getID() {
return this.studentID;
}
}
public static void main(String[] args) {
Student s = new Student(1, 2019, "Tanaka", "Taro");
System.out.println(s.getID());
System.out.println(s.getUserName());
}
図32のコンストラクタの宣言(図34)を見れば分かるように、スーパークラスのコンストラクタが引数を必要としている場合は、自コンストラクタの引数を渡すことで対応しなければなりません。また、図33のソースコードを見れば分かるように、Studentクラスに定義していないスーパークラスに定義されているgetUserNameメソッドが呼び出すことができます。
このように、少なくともスーパークラスでpublicなアクセス修飾子が設定されているメンバーであれば、サブクラスから呼び出すことが可能です。
public Student(int no, int entryYear, String familyName, String firstName) {
super(familyName, firstName);
[7]: エドガー・ダイクストラ, トニー・ホーア, オーレ・ヨハン・ダール, ``構造化プログラミング'', サイエンス社 (1975)
継承は、最初期(特にSimula 67)においては連接(concatenation)と呼ばれていました[7]。Simula 67においてはクラスとクラスの連接だけでなく、クラスとブロックの連接が用意されており、厳密には今日の継承とは異なっていたことも関係しています。