インヘリタンス

Java基礎 9

1.本章での学習項目

本章ではオブジェクト指向でプログラムするために重要な機能である、インヘリタンス(継承)に関して学習します。

2.本章の講義

本章で学習する内容を動画としてまとめたものです。最初に一通り見終わった後で、学習に入るようにしてください。

講義:インヘリタンス

3.インヘリタンス(継承)

オブジェクト指向言語は、似たようなプログラムコードをできるだけ使いまわそうという考え方を持っています。

プログラムの1単位であるクラスも、同様の処理を持つクラスをいくつか記述する必要がある場合、一つのクラスのプログラムソースを使い回す技術があります。
この技術の一つがこれから学習するインヘリタンス(継承)です。
継承は、新しくクラスを作成するときに、すでに存在する他のクラスのフィールドやメソッドをそのまま受け継ぐことができる手法です。

例えば、以下の図のように、クラスMySuperにフィールドxとメソッドgetXが定義してあるとき、このインヘリタンスの手法を用いることで、フィールドxとメソッドgetXをクラスMySubがそのまま引き継ぐことができます。
そのため、クラスMySubは、クラスMySuperのフィールドとメソッドを特に記述していなくても、定義してあることになります。
継承の元となるクラスを親クラス(スーパークラス)と呼び、それを継承するクラスを子クラス(サブクラス)と呼びます。


inheritance1

以下がインヘリタンス(継承)の書式です。子クラス(サブクラス)を定義するときに、「extends」を使用し、親クラス(スーパークラス)を継承します。

public class 子クラス extends 親クラス {
	// (省略)
}

「MySuper」クラスを継承した「MySub」クラスは以下のようになります。
まずは、親クラス(スーパークラス)の「MySuper」クラスの定義です。

MySuper.java
public class MySuper {
	// フィールドxの定義
	int x = 10;

	// メソッドgetXの定義
	int getX() {
		return x;
	}
}

次に子クラス(サブクラス)の「MySub」クラスの定義です。

MySub.java
// MySuperクラスを継承
public class MySub extends MySuper {

	// 親クラス「MySuper」クラスのフィールドxとメソッドgetXは、
	// 記述がなくても、定義されています

	// フィールドyの定義
	int y = 20;

	// メソッドgetYの定義
	int getY(){
			return y;
	}
}

子クラス(サブクラス)である「MySub」は、「MySuper」を継承することによって、メンバ(フィールドやメソッド)を何も記述しなくても、親クラス(スーパークラス)である「MySuper」のフィールドやメソッドが定義されていることになります。
また、上記の図のように、子クラス(サブクラス)は、自分独自のフィールドやメソッドを定義することにより、継承したメンバと追加したメンバの両方が定義されていることになります。
なお、コンストラクタは引き継がれませんので注意しましょう。

それでは、子クラス(サブクラス)「MySub」に、利用するクラス「MyClass」からアクセスし、動作を確認してみましょう。継承したフィールドやメソッドも記述されていなくても、同様に利用できることがわかります。

MyClass.java
public class MyClass {

	public static void main(String[] args) {
		// MySubクラスをインスタンス化
		MySub mysub = new MySub();

		// MySubクラスが継承したフィールドxにアクセス
		System.out.println(mysub.x);

		// MySubクラスが継承したメソッドgetXにアクセス
		System.out.println(mysub.getX());

		// MySubクラスで定義したフィールドyにアクセス
		System.out.println(mysub.y);

		//  MySubクラスで定義したメソッドgetYにアクセス
		System.out.println(mysub.getY());
	}
}

実行結果は、以下のとおりとなります。

実行結果
10
10
20
20

インヘリタンス(継承)を利用することにより、重複するフィールドやメソッドの記述を省略することができるため、プログラミングの効率が良くなります。
また、何か修正があった場合に、親クラスのみを修正するだけで済むなどメンテナンス性が向上したりするメリットがあります。

Information

Javaの継承の特徴
  1. 継承は、何世代でもOK

    Javaは、「MySub」クラスの子クラスとして、「MySubSub」クラスを、そのまた子クラスとして、「MySubSubSub」クラスを・・というように、継承を何世代も行うことができます。
    その場合、途中で定義されているフィールドやメソッドは、延々と引き継がれることになります。
    しかし、実際のクラス設計では、継承を何度も行うと、どんなフィールドやメソッドが引き継がれているのか、わかりにくくなってしまいますので、あまり何世代にもわたった継承はオススメできません。
  2. 多重継承不可

    もう一つ重要なJavaの継承の特徴として、Javaは、多重継承不可という点です。
    Javaは、子クラスを複数持つことは、問題ありませんが、親クラスは、一つだけという制約があります。
    つまり、あるクラスを継承したら、他のクラスを継承することはできないのです。
    これも、継承の場合、継承されたフィールドやメソッドが子クラスには、実際に記述されないので、多くのクラスから継承されるとわかりにくくなってしまうことが一つの理由です。

継承は、プログラムを効率良く作成するには、便利な手法ですが、使用には気をつけないと大変わかりにくいことになりますので、注意しましょう。

4.すべてのクラスの親クラス

java.lang.Objectクラスは、全てのクラスの親クラスとして位置しています。

一見何も継承していないように見えるクラスでも、java.lang.Objectクラスの子クラスになります。
これはJava言語の決まりで「全てのクラスはjava.lang.Objectが自動的に継承される」ことになっているためです。

つまり、以下の2つのクラスは同じような意味を表します。

public class MyClass {
}

public class MyClass extends java.lang.Object {
}

ただしこの2つの宣言は、完全に同じではありません。
Java言語は、既に説明したとおり複数の親クラスを保持できないので、一度extendsでObjectクラスを継承してしまうと、他のクラスを継承できないことになります。
そのため、最初のMyClassでは、extendsを追加して他のクラスを継承できますが、java.lang.Objectを明示的にextendsした場合は、別のクラスの継承を追加できません。
しかし、どちらもjava.lang.Objectを継承しているという意味では同じとなります。
全てのクラスは、Objectクラスのメソッドを使うことができます。
よく使うメソッドに、toStringメソッドがあります。このメソッドは、オブジェクトの文字列表現を戻すメソッドです。

それでは、以下のサンプルソースを見てみましょう。

MyClass.java
public class MyClass {
}
UseClass.java
public class UseClass {

	public static void main(String[] args) {
		MyClass myclass = new MyClass();
		String s = myclass.toString();
	}
}

MyClassには何もメソッドが定義されていませんが、上記のようにjava.lang.Objectクラスで定義されているtoStringメソッドを呼び出すことができます。
つまり、MyClassが自動的にjava.lang.Objectの子クラスになっているということになります。

5.this と super

インスタンスメソッドでは、thisやsuperという特殊な参照型変数があります。
このthisやsuperを使い、フィールド、メソッド、コンストラクタを呼び出すことが可能です。
これから、これらの使い方を学習していきましょう。

5.1.this

「this」は自分自身のインスタンスの参照値を変数として持っています。thisはインスタンスメソッド内でのみ使用できます。
「this」変数は、this変数が出現するオブジェクト自身を指す特別な変数で、よく利用するケースとしては、メソッド内部で定義したローカル変数名とそのメソッド自身が属しているクラスのインスタンス変数名がバッティングしたときです。
メソッド内で使用するインスタンス変数(フィールド)とローカル変数が同じ名前の場合、ローカル変数名が優先して使用されるため、インスタンス変数(フィールド)であることを明示するために、「this」を使用します。

以下がサンプルソースです。ローカル変数xとフィールドxは、変数名が同じであるため、ローカル変数が属しているメソッドの中でフィールドを使用する場合には、「this.x」というように記述し、「このオブジェクトに定義されているフィールドx」であることを明示します。

MyClass2.java
public class MyClass2 {

	public static void main(String[] args) {
		// MyThis2クラスをインスタンス化
		MyThis2 mythis = new MyThis2();

		// MyThis2クラスのprintメソッド呼び出し
		mythis.print(100);
	}
}
MyThis2.java
public class MyThis2 {

	// フィールドxを定義
	int x = 1;

	// ローカル変数xを定義
	void print(int x) {
		// フィールドxの値1を出力
		System.out.println(this.x);

		// ローカル変数xの値100を出力
		System.out.println(x);

		// フィールドの値1とローカル変数の値100を加算し、
		// フィールドに代入
		this.x = this.x + x;

		// フィールドの値101を出力
		System.out.println(this.x);
	}
}

実行結果は、以下のとおりとなります。

実行結果
1
100
101

5.2.super

「super」は親クラスのインスタンス参照値を変数として持っています。superもインスタンスメソッド内で使用することができます。
「super」変数は、親クラスのフィールドやメソッドを明示的に示す場合に使用します。例えば、親クラスと子クラスで同じ名前のフィールドやメソッドが定義されている場合、子クラスのオブジェクトでは、自身のフィールドやメソッドが優先的に使用されます。
子クラスから、親クラスのフィールドやメソッドに明示的にアクセスする場合には、superを使用します。

以下がサンプルソースです。

MySuper1.java
// 親クラスMySuper1を定義
public class MySuper1 {
	// フィールドxを定義
	int x = 100;

	void print() {
		System.out.println("superの" + x);
	}
}
MySub1.java
// 子クラスMySub1を定義:MySuper1を継承
public class MySub1 extends MySuper1 {
	// フィールドxを定義
	int x = 1;

	void print() {
		// 親クラスのフィールドxの値100を出力
		System.out.println(super.x);

		// 自身のフィールドxの値1を出力
		System.out.println(x);

		// 親クラスのprintメソッド呼び出し
		super.print();
	}
}
MyClass3.java
public class MyClass3 {

	public static void main(String[] args) {
		// MySub1クラスをインスタンス化
		MySub1 mysub = new MySub1();

		// MySub1クラスのprintメソッド呼び出し
		mysub.print();
	}
}

実行結果は、以下のとおりとなります。

実行結果
100
1
superの100

5.3.コンストラクタの呼び出し

thisメソッド「this()」は自分のクラスのコンストラクタを呼び出すことができます。
この方法を利用して、コンストラクタの中で同じクラスの他のコンストラクタを呼び出すことができ、引数の引渡しも可能です。

superメソッド「super()」は親クラスのコンストラクタを呼び出すことができます。
この方法を利用して、コンストラクタの中で親クラスのコンストラクタを呼び出すことができ、引数の引渡しも可能になります。

ただし、thisメソッド、superメソッドともに、使えるのはコンストラクタの一番最初の行だけとなります。
他の場所で使うとコンパイルエラーになりますので注意しましょう。

superメソッドの仕組みとして、クラスのインスタンスが生成されると、その親クラスのコンストラクタも呼び出されるので、コンストラクタの呼び出しが連鎖的に行なわれます。
これは、コンストラクタの中には暗黙的にsuperメソッドの「super()」という記述がコンストラクタの一番最初の行に自動的に挿入されているためです。
ただし、「super()」という処理は全てのコンストラクタに挿入されるわけではなく、以下の2つの条件に当てはまる場合のみが該当します。

  • super()の記述が無い場合
  • this()の記述が無い場合

つまり、明示的に自分のクラスまたは親クラスのコンストラクタの呼び出しが記述されていない場合のみ、「super()」という処理が自動的に挿入されます。

なお、継承した親クラスに引数つきのコンストラクタしかない場合には、「super()」に対応するコンストラクタが存在しないため、親クラスの引数つきコンストラクタを明示的に呼び出す必要があります。
親クラスの引数つきコンストラクタを呼ぶには、「super()」の括弧部分に対応するデータ型の引数を与えることで呼び出すことができます。
以下のサンプルソースのように明示的に親クラスのコンストラクタを呼び出すことで、連鎖的な呼び出しを保つことができます。

MyTest1.java
public class MyTest1 {
	MyTest1() {
		System.out.println("MyTest1のコンストラクタ");
	}
}
MyTest2.java
public class MyTest2 extends MyTest1 {
	MyTest2(int x){
		// 暗黙的にsuper()が挿入され、MyTest1の引数なしコンストラクタが呼び出される
		// super();
		System.out.println("MyTest2の引数つきコンストラクタ");
	}
}
MyTest3.java
public class MyTest3 extends MyTest2 {
	MyTest3() {
		// MyTest2クラスの引数つきコンストラクタの明示的な呼び出し
		// MyTest2クラス(親クラス)に引数つきコンストラクタしかないため、この行を記述しないと、エラーになります。
		super(3);

		System.out.println("MyTest3のコンストラクタ");
	}
}
MyClass4.java
public class MyClass4 {
	public static void main(String[] args) {
		MyTest3 myTest3 = new MyTest3(); // クラス MyTest3 をインスタンス化
	}
}

実行結果は、以下のとおりとなります。

実行結果
MyTest1のコンストラクタ
MyTest2の引数つきコンストラクタ
MyTest3のコンストラクタ

次に、「this()」を使ったサンプルソースです。
thisは自分自身の参照を表し、「this()」は自分のコンストラクタを呼び出せます。
このサンプルではmainメソッドで呼び出されているのは、MyTest5のコンストラクタのみです。
このコンストラクタの中では「this(3)」を使って、もうひとつの引数つきのコンストラクタを呼んでいます。

MyTest4.java
public class MyTest4 {

	void myMsg() {
		System.out.println("MyTest4のmyMsgメソッド");
	}
}
MyTest5.java
public class MyTest5 extends MyTest4 {
	MyTest5() {
		this(3); // MyTest5 の引数つきコンストラクタの呼びだし
		super.myMsg(); // MyTest4 の myMsg メソッドの呼びだし
		this.myMsg(); // MyTest5 の myMsg メソッドの呼びだし
	}
	MyTest5(int x) {
		System.out.println("MyTest5の引数つきコンストラクタ");
	}
	void myMsg() {
		System.out.println("MyTest5のmyMsgメソッド");
	}
}
MyClass5.java
public class MyClass5 {

	public static void main(String[] args) {
		// クラス MyTest5 をインスタンス化
		MyTest5 myTest5 = new MyTest5();
	}
}

実行結果は、以下のとおりとなります。

実行結果
MyTest5の引数つきコンストラクタ
MyTest4のmyMsgメソッド
MyTest5のmyMsgメソッド

このように、クラスはシグネチャーの違うコンストラクタを複数定義できます(このことをオーバーロードといいます。詳しくは「ポリモフィズム」の章で説明します)。

6.final修飾子

最後に、final修飾子について説明します。
final修飾子は、継承されたくないクラスやフィールド、メソッドの前に付加することにより、継承をできなくすることができます。
また、「ポリモフィズム」の章で学習するオーバーライドもできなくなります。そのため、これ以上機能拡張や変更がされたくない場合に使います。

例えば、final修飾子がついたクラスは、親クラスになることができません。
以下のサンプルソースのように、final修飾子が付加されたMyExtendsクラスを継承しようとするとコンパイルエラーになります。

// final修飾子を付加したクラス
final public class MyExtends {
}
// final修飾子を付加したクラスは継承できないのでエラー
public class SubClass extends MyExtends {
}

親クラスでfinal修飾子がついたメソッドは、子クラスで同じメソッドを定義することはできません。
また、final修飾子がついたフィールドは、子クラスで値を変更することはできません。

MyExtends.java
public class MyExtends {
	// finalフィールド
	final int x = 1;

	// final メソッド
	final void myTemp() {
	}
}
SubClass.java
public class SubClass extends MyExtends {
	void change() {
		// 親クラスで finalフィールド定義されているのでエラー
		x = 10;
	}
	// 親クラスで finalメソッド定義されているのでエラー
	void myTemp(){
	}
}

7.練習問題1

それでは、練習問題をやってみましょう。
ここでは、「カプセル化」の章の練習問題で作成したCatクラスとDogクラスを使用していきます。

7.1.問題1

CatクラスとDogクラスに共通に定義されているフィールドとメソッドを抜き出した親クラスとして、Petクラスを以下のとおりに作成してください。

パッケージ名 petpac
クラス名 Pet
フィールド private String syurui ペットの種類
private String seibetsu ペットの性別
private int toshi ペットの年
コンストラクタ public Pet() 下記内容で各フィールドを初期化する

  • syurui = "なぞの生物"
  • seibetsu = "性別不明"
  • toshi = 0
public Pet(String syurui, String seibetsu, int toshi) 各引数でフィールドを初期化する
メソッド public String getSyurui() ペットの種類を返す
public String getSeibetsu() ペットの性別を返す
public int getToshi() ペットの年を返す

Pet.java
package petpac;

public class Pet {
	// フィールド定義
	private String syurui; // 生物の種類
	private String seibetsu; // 生物の性別
	private int toshi; // 生物の年齢

	// 引数なしコンストラクタ定義(各フィールドを指定した値で初期化)
	public Pet() {
		syurui = "なぞの生物";
		seibetsu = "性別不明";
		toshi = 0;
	}

	// 引数ありコンストラクタ定義(各フィールドを引数で初期化)
	public Pet(String syurui, String seibetsu, int toshi) {
		this.syurui = syurui;
		this.seibetsu = seibetsu;
		this.toshi = toshi;
	}

	// フィールドsyuruiの値を戻すメソッド
	public String getSyurui() {
		return syurui;
	}

	// フィールドseibetsuの値を戻すメソッド
	public String getSeibetsu() {
		return seibetsu;
	}

	// フィールドtoshiの値を戻すメソッド
	public int getToshi() {
		return toshi;
	}
}

フィールド、コンストラクタ、メソッドの定義は、各構文の形式通りに記述します。

7.2.問題2

問題1で作成したPetクラスを親クラスとして継承するように、CatクラスとDogクラスを以下のように修正してください。
なお、Petクラスは修正しないでください。

パッケージ名 catpac
クラス名 Cat
親クラス名 Pet
フィールド Petクラスから継承するため記述は不要
コンストラクタ public Cat() 下記内容で各フィールドを初期化する

  • syurui = "スコティッシュフォールド"
  • seibetsu = "メス"
  • toshi = 0
public Cat(String syurui, String seibetsu, int toshi) 親クラスPetのフィールドを各引数で初期化します。
メソッド Petクラスから継承するため記述は不要
パッケージ名 dogpac
クラス名 Dog
親クラス名 Pet
フィールド Petクラスから継承するため記述は不要
コンストラクタ public Dog() 下記内容で各フィールドを初期化する

  • syurui = "トイプードル"
  • seibetsu = "メス"
  • toshi = 0
public Dog(String syurui, String seibetsu, int toshi) 親クラスPetのフィールドを各引数で初期化します。
メソッド Petクラスから継承するため記述は不要

CatクラスとDogクラスの修正後、shopパッケージに以下のPetShopクラスを新規で作成し、プログラムをそのまま記述して、実行結果と同様の出力結果が表示されるようにしてください。

PetShop.java
package shop;

import catpac.Cat;
import dogpac.Dog;
import petpac.Pet;

public class PetShop {

	public static void main(String[] args) {

		Pet myPet = new Pet();
		PetShop.buyPet(myPet);

		System.out.println("---");

		Cat myCat = new Cat();
		PetShop.buyPet(myCat);

		myCat = new Cat("アメリカンショートヘアー","オス", 0);
		PetShop.buyPet(myCat);

		System.out.println("---");

		Dog myDog = new Dog();
		PetShop.buyPet(myDog);

		myDog = new Dog("ゴールデンリトリバー","オス", 0);
		PetShop.buyPet(myDog);
	}

	private static void buyPet(Pet tPet){
		System.out.println(tPet.getSyurui()
			+ "の"
			+ tPet.getSeibetsu()
			+ "、"
			+ tPet.getToshi() + "歳を買いました。");

	}
}
実行結果
なぞの生物の性別不明、0歳を買いました。
---
スコティッシュフォールドのメス、0歳を買いました。
アメリカンショートヘアーのオス、0歳を買いました。
---
トイプードルのメス、0歳を買いました。
ゴールデンリトリバーのオス、0歳を買いました。

※この問題の解答は掲載しておりません。Tech Fun ITスクールのJava研修では、講師が丁寧に解説しています。

8.本章のまとめ

  • 「インヘリタンス(継承)」とは、新しくクラスを作成するときに、すでに存在する他のクラスのフィールドやメソッドをそのまま受け継ぐことができる手法のこと。
  • 継承するときは、クラス定義をする際に「extends」を使用する。
  • 継承することで、継承元のクラス(親クラス)に定義されているフィールドやメソッドが使用可能となる。
  • 自分のクラスのフィールドやメソッドを指す場合は「this」を使って、同じ名前のローカル変数や引数と区別する必要がある。
  • 親クラスのフィールドやメソッドを明示的に指す場合は、「super」を用いる。
  • 「this()メソッド」は、自分のクラスのコンストラクタを呼び出すときに使用する。
  • 親クラスのコンストラクタを呼び出す場合は「super()メソッド」を使用する。
  • this()メソッド、super()メソッドともに、使えるのはコンストラクタの一番最初の行だけである。
  • 「final」を付けると、クラスやメソッドなどの継承ができなくなる。

使い方に不安がある場合は、教材のサンプルをもう一度動かしてみるなどして復習しておくと良いでしょう。

インヘリタンスについての説明は、以上です。

執筆・編集

Tech Fun Magazine編集部
Tech Funの現役のITエンジニアが、システム開発の基礎知識や実践的なノウハウを執筆・編集しています。
Tech Fun ITスクールの研修講師として活躍するメンバーもおり、プログラミング初心者がつまづきやすいポイントを丁寧に解説しています。

ARTICLE
記事一覧

Java基礎

Java12からJava17までに導入された機能の紹介

システム開発の基本

プログラミングとテストの要点

データベース環境構築

データベース環境構築(Windows版) テストデータ作成

データベース環境構築

データベース環境構築(Windows版) MariaDBの設定

データベース環境構築

データベース環境構築(Mac版) テストデータ作成

データベース環境構築

データベース環境構築(Mac版) MariaDBの設定

Java基礎

はじめてのJava

Java基礎

Javaのデバッグ方法

Java基礎

ポリモフィズム

記事一覧を見る