サブクラスを作る

みるくあいらんどっ! > ドキュメント > Java > じっくり学ぶ Java講座 [初心者向け・入門]


サブクラスで新しいメソッドを宣言する

猫クラスを拡張する

猫クラスを拡張することにします。新しく、賢い猫(WiseCat)クラスを作成し、既存の猫クラスを継承することにします。さらに、WiseCatクラスでは、lieメソッドによって「横になる」という動作をできるようにします。

なお、これまでの猫クラスは継承されることを想定せずに作成していました。具体的にはインスタンスフィールド(name, cryCount)のアクセス修飾子は privateになっていました。これを protectedに変更することとします。なお、アクセス修飾子の詳細については、後の章にて解説します。

ソースコードは以下の通り。

M201/Cat.java

/**
 * 猫を表すクラスです。
 */
public class Cat {
	/**
	 * 名前。
	 */
	protected String name;
	/**
	 * 鳴く回数。
	 */
	protected int cryCount;
	
	/**
	 * コンストラクタ。
	 */
	public Cat() {
		this("名無しの猫", 1);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 */
	public Cat(String name) {
		this(name, 1);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 * @param cryCount 鳴く回数
	 */
	public Cat(String name, int cryCount) {
		this.setName(name);
		this.setCryCount(cryCount);
	}
	
	/**
	 * 名前を設定します。
	 * @param name 名前
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * 鳴く回数を設定します。
	 * @param cryCount 鳴く回数
	 */
	public void setCryCount(int cryCount) {
		this.cryCount = cryCount;
	}
	
	/**
	 * 鳴きます。
	 */
	public void cry() {
		System.out.print(this.name + "「");
		for(int i = 0; i < this.cryCount; i ++) {
			System.out.print("ニャー");
		}
		System.out.println("」");
	}
}

M201/WiseCat.java

/**
 * 賢い猫を表すクラスです。
 */
public class WiseCat extends Cat {
	/**
	 * 横になります。
	 */
	public void lie() {
		System.out.println(this.name + "は横になりました。");
	}

}

M201/M201.java

/**
 * Catクラスと WiseCatクラスを利用します。
 */
public class M201 {
	
	/**
	 * メインメソッド。
	 * @param args 引数
	 */
	public static void main(String[] args) {
		// インスタンスを作成
		Cat cat1 = new Cat();
		Cat cat2 = new Cat("ミケ", 4);
		WiseCat wiseCat1 = new WiseCat();
		WiseCat wiseCat2 = new WiseCat();
		wiseCat2.setName("タマ");
		wiseCat2.setCryCount(5);
		
		// 猫のインスタンスメソッドを実行
		cat1.cry();
		cat2.cry();
		
		// 賢い猫のインスタンスメソッドを実行
		wiseCat1.cry();
		wiseCat1.lie();
		wiseCat2.cry();
		wiseCat2.lie();
	}
}

実行結果は以下の通り。

名無しの猫「ニャー」
ミケ「ニャーニャーニャーニャー」
名無しの猫「ニャー」
名無しの猫は横になりました。
タマ「ニャーニャーニャーニャーニャー」
タマは横になりました。

まず、数を数えられることが大切です。Catクラスと WiseCatクラスについて、以下の数を正確に数えられるでしょうか。

  • Catクラス
    • 宣言された staticなフィールド…0個
    • 宣言された staticなメソッド…0個
    • 宣言されたインスタンスフィールド…2個
    • 宣言されたインスタンスメソッド…3個
    • コンストラクタ…3種類
  • WiseCatクラス
    • 宣言された staticなフィールド…0個
    • 宣言された staticなメソッド…0個
    • 宣言されたインスタンスフィールド…0個(Catクラスで宣言されたものも合算すると 2個)
    • 宣言されたインスタンスメソッド…1個(Catクラスで宣言されたものも合算すると 4個)
    • コンストラクタ…1種類(デフォルトコンストラクタが補完されています)

その他に以下のことが理解できるでしょうか。

  • WiseCatクラスは Catクラスを継承している。
  • WiseCatクラスは Catクラスのインスタンスフィールドを継承している。その証拠に、lieメソッドの中で this.nameを使用できている。
  • WiseCatクラスは Catクラスのインスタンスメソッドを継承している。その証拠に、WiseCat型インスタンスに対してcryメソッドを実行することができる。
  • WiseCatクラスは Catクラスのコンストラクタは継承していない。コンストラクタをまったく記述していないので、デフォルトコンストラクタが補完されている。
  • メインメソッドでは、Cat型インスタンスを 2個、WiseCat型インスタンスを 2個、生成している。
  • 継承されたからと言って、スーパークラス側の Catクラスの挙動には特に変化は無い。

コンストラクタと superの省略

WiseCatクラスにはコンストラクタの記述がありませんので、デフォルトコンストラクタが補完されます。デフォルトコンストラクタの補完内容は以下のような感じでした。

	public WiseCat() {
		; // 何もしない
	}

しかし、ただ何もしないのではないのです。実はもうひとつ省略があります。デフォルトコンストラクタに限らず、すべてのコンストラクタについて、「コンストラクタ内の最初の行が super(~)でも this(~)でもない場合には、super();が補完される」というルールがあります。具体的には、以下のようになります。なお、superは予約語です。

	public WiseCat() {
		super(); // スーパークラスの引数無しコンストラクタを実行する
		; // 何もしない
	}

これによって、WiseCat型インスタンスを作成した際、WiseCatクラスのコンストラクタが実行された直後に Cat型の引数なしのコンストラクタが実行されます。このことで、インスタンスフィールドである nameに、「名無しの猫」が設定されることになります。また、実は Catクラスのコンストラクタにおいても、Cat.javaの 34行目と 35行目の間に super();が省略されており、そのスーパークラスである Objectクラスの引数無しコンストラクタが実行されていることになります。

super(~)の記述は、コンストラクタ内の最初に記述しなければなりません。this(~)のときと同様に、コンストラクタ内の 2行目以降に記述するとビルドエラーになります。例えば、以下のような記述ではビルドエラーになります。

	/**
	 * コンストラクタ。
	 */
	public WiseCat() {
		(何らかの処理);
		super(); // ビルドエラー
	}

なお、本ウェブサイトでは、今回のような「super();」については、一貫して省略することとします。

コンストラクタを作る

Catクラスには 3種類のコンストラクタがありました。WiseCatクラスにも同様に、3種類のコンストラクタを準備することにします。

ソースコードは以下の通り。

M202/Cat.java

/**
 * 猫を表すクラスです。
 */
public class Cat {
	/**
	 * 名前。
	 */
	protected String name;
	/**
	 * 鳴く回数。
	 */
	protected int cryCount;
	
	/**
	 * コンストラクタ。
	 */
	public Cat() {
		this("名無しの猫", 1);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 */
	public Cat(String name) {
		this(name, 1);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 * @param cryCount 鳴く回数
	 */
	public Cat(String name, int cryCount) {
		this.setName(name);
		this.setCryCount(cryCount);
	}
	
	/**
	 * 名前を設定します。
	 * @param name 名前
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * 鳴く回数を設定します。
	 * @param cryCount 鳴く回数
	 */
	public void setCryCount(int cryCount) {
		this.cryCount = cryCount;
	}
	
	/**
	 * 鳴きます。
	 */
	public void cry() {
		System.out.print(this.name + "「");
		for(int i = 0; i < this.cryCount; i ++) {
			System.out.print("ニャー");
		}
		System.out.println("」");
	}
}

M202/WiseCat.java

/**
 * 賢い猫を表すクラスです。
 */
public class WiseCat extends Cat {
	/**
	 * コンストラクタ。
	 */
	public WiseCat() {
		; // super(); が省略されている
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 */
	public WiseCat(String name) {
		super(name);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 * @param cryCount 鳴く回数
	 */
	public WiseCat(String name, int cryCount) {
		super(name, cryCount);
	}
	
	/**
	 * 横になります。
	 */
	public void lie() {
		System.out.println(this.name + "は横になりました。");
	}

}

M202/M202.java

/**
 * Catクラスと WiseCatクラスを利用します。
 */
public class M202 {
	
	/**
	 * メインメソッド。
	 * @param args 引数
	 */
	public static void main(String[] args) {
		// インスタンスを作成
		Cat cat1 = new Cat();
		Cat cat2 = new Cat("ミケ", 4);
		WiseCat wiseCat1 = new WiseCat();
		WiseCat wiseCat2 = new WiseCat("タマ", 5);
		
		// 猫のインスタンスメソッドを実行
		cat1.cry();
		cat2.cry();
		
		// 賢い猫のインスタンスメソッドを実行
		wiseCat1.cry();
		wiseCat1.lie();
		wiseCat2.cry();
		wiseCat2.lie();
	}
}

実行結果は以下の通り。

名無しの猫「ニャー」
ミケ「ニャーニャーニャーニャー」
名無しの猫「ニャー」
名無しの猫は横になりました。
タマ「ニャーニャーニャーニャーニャー」
タマは横になりました。

まず、数を数えられることが大切です。Catクラスと WiseCatクラスについて、以下の数を正確に数えられるでしょうか。

  • Catクラス
    • 宣言された staticなフィールド…0個
    • 宣言された staticなメソッド…0個
    • 宣言されたインスタンスフィールド…2個
    • 宣言されたインスタンスメソッド…3個
    • コンストラクタ…3種類
  • WiseCatクラス
    • 宣言された staticなフィールド…0個
    • 宣言された staticなメソッド…0個
    • 宣言されたインスタンスフィールド…0個(Catクラスで宣言されたものも合算すると 2個)
    • 宣言されたインスタンスメソッド…1個(Catクラスで宣言されたものも合算すると 4個)
    • コンストラクタ…3種類

今回は、メインメソッドにおいて WiseCat型インスタンスを 2個生成しており、3種類のコンストラクタの内の 2種類を利用しています。WiseCatクラスの 3種類のコンストラクタでは、それぞれ異なる super(~)を記述することで、それぞれに最も適したスーパークラスのコンストラクタを呼び出していることが分かるでしょうか。

サブクラスで既存のメソッドをオーバーライドする

犬クラスを拡張する (1)

猫クラスと同様に、犬クラスを拡張することにします。新しく、賢い犬(WiseDog)クラスを作成し、既存の犬クラスを継承することにします。さらに、WiseCatクラスでは、touchメソッドによって「お手をする」という動作をできるようにし、その上、既存の鳴き方(cryメソッド)を変更します(オーバーライドします)。

なお、これまでの犬クラスは継承されることを想定せずに作成していました。具体的にはインスタンスフィールド(name, cryCount)のアクセス修飾子は privateになっていました。これを protectedに変更することとします。なお、アクセス修飾子の詳細については、後の章にて解説します。

ソースコードは以下の通り。

M203/Dog.java

/**
 * 犬を表すクラスです。
 */
public class Dog {
	/**
	 * 名前。
	 */
	protected String name;
	/**
	 * 鳴く回数。
	 */
	protected int cryCount;
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 */
	public Dog(String name) {
		this(name, 1);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 * @param cryCount 鳴く回数
	 */
	public Dog(String name, int cryCount) {
		this.setName(name);
		this.setCryCount(cryCount);
	}
	
	/**
	 * 名前を設定します。
	 * @param name 名前
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * 鳴く回数を設定します。
	 * @param cryCount 鳴く回数
	 */
	public void setCryCount(int cryCount) {
		this.cryCount = cryCount;
	}
	
	/**
	 * 鳴きます。
	 */
	public void cry() {
		System.out.print(this.name + "「");
		for(int i = 0; i < this.cryCount; i ++) {
			System.out.print("ワン");
		}
		System.out.println("」");
	}
	
	/**
	 * お座りします。
	 */
	public void sitDown() {
		System.out.println(this.name + "は座りました。");
	}
}

M203/WiseDog.java

/**
 * 賢い犬を表すクラスです。
 */
public class WiseDog extends Dog {
	/**
	 * コンストラクタ。
	 * @param name 名前
	 */
	public WiseDog(String name) {
		super(name);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 * @param cryCount 鳴く回数
	 */
	public WiseDog(String name, int cryCount) {
		super(name, cryCount);
	}
	
	@Override
	public void cry() {
		System.out.print(this.name + "「");
		for(int i = 0; i < this.cryCount; i ++) {
			System.out.print("キャン");
		}
		System.out.println("」");
	}
	
	/**
	 * お手をします。
	 */
	public void touch() {
		System.out.println(this.name + "はお手をしました。");
	}
}

M203/M203.java

/**
 * Dogクラスと WiseDogクラスを利用します。
 */
public class M203 {
	
	/**
	 * メインメソッド。
	 * @param args 引数
	 */
	public static void main(String[] args) {
		// インスタンスを作成
		Dog dog1 = new Dog("ポチ");
		Dog dog2 = new Dog("クロ", 5);
		WiseDog wiseDog1 = new WiseDog("ラッキー");
		WiseDog wiseDog2 = new WiseDog("ハチ", 10);
		
		// 犬のインスタンスメソッドを実行
		dog1.cry();
		dog1.sitDown();
		dog2.cry();
		
		// 賢い犬のインスタンスメソッドを実行
		wiseDog1.cry();
		wiseDog1.sitDown();
		wiseDog1.touch();
		wiseDog2.cry();
		wiseDog2.sitDown();
		wiseDog2.touch();
	}
}

実行結果は以下の通り。

ポチ「ワン」
ポチは座りました。
クロ「ワンワンワンワンワン」
ラッキー「キャン」
ラッキーは座りました。
ラッキーはお手をしました。
ハチ「キャンキャンキャンキャンキャンキャンキャンキャンキャンキャン」
ハチは座りました。
ハチはお手をしました。

まず、数を数えられることが大切です。Dogクラスと WiseDogクラスについて、以下の数を正確に数えられるでしょうか。

  • Dogクラス
    • 宣言された staticなフィールド…0個
    • 宣言された staticなメソッド…0個
    • 宣言されたインスタンスフィールド…2個
    • 宣言されたインスタンスメソッド…4個
    • コンストラクタ…2種類
  • WiseDogクラス
    • 宣言された staticなフィールド…0個
    • 宣言された staticなメソッド…0個
    • 宣言されたインスタンスフィールド…0個(Dogクラスで宣言されたものも合算すると 2個)
    • 宣言されたインスタンスメソッド…2個(Dogクラスで宣言されたものも合算すると 5個。4 + 2 = 5になるのは、1個のメソッドがオーバーライドされているため)
    • コンストラクタ…2種類

WiseDogクラスは、cryメソッドがオーバーライドされていること、また touchメソッドが宣言されていることが分かるでしょうか。

コンストラクタ引数問題

M201プロジェクトの Catクラスを継承して SpecialCatクラスを作成するとします。この SpecialCatクラスには、メソッドもフィールドもコンストラクタも宣言しないこととします。この場合、ソースコードは以下となります。

public class SpecialCat extends Cat {
	
}

これは正しくビルドされます。また、新たに宣言されたメソッドやフィールドはゼロですが、コンストラクタはデフォルトコンストラクタが補完されます。つまり、実際には以下のソースコードを記述したのと同じような感じになります。

public class SpecialCat extends Cat {
	
	/**
	 * コンストラクタ。
	 */
	public SpecialCat() {
		super();
	}
}

同様に M203プロジェクトの Dogクラスを継承して SpecialDogクラスを作成するとします。この SpecialDogクラスには、メソッドもフィールドもコンストラクタも宣言しないこととします。この場合、ソースコードは以下となります。

public class SpecialDog extends Dog {
	
}

しかし、これはビルドエラーになります。SpecialCatの場合には問題なくビルドできたのに、何故、SpecialDogの場合はビルドエラーになってしまうのか、理由は分かるでしょうか。それは、デフォルトコンストラクタが絡んできます。まず、デフォルトコンストラクタが補完された後のソースコードを見てみましょう。

public class SpecialDog extends Dog {
	
	/**
	 * コンストラクタ。
	 */
	public SpecialDog() {
		super();
	}
}

問題点が分かったでしょうか。7行目のsuper();が問題になるのです。と言うのも、Dogクラスは「野良犬は認められない」という理由で、引数無しのコンストラクタをあえて作成しませんでした。しかし、サブクラス(SpecialDogクラス)のデフォルトコンストラクタでは、スーパークラス(Dogクラス)の引数無しのコンストラクタを呼び出そうとしているのです。ところが、引数無しのコンストラクタは Dogクラスに存在しませんから、ビルドエラーになってしまいます。

今回の場合、SpecialDogクラスには適切なコンストラクタをひとつ以上作成する必要がある、ということになります。例えば、以下のような感じで作成する必要があります。

public class SpecialDog extends Dog {
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 */
	public SpecialDog(String name) {
		super(name);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 * @param cryCount 鳴く回数
	 */
	public SpecialDog(String name, int cryCount) {
		super(name, cryCount);
	}
}

このように、引数無しのコンストラクタが存在しないクラスを継承してサブクラスを作成する場合には、少しの注意が必要になります。

スーパークラスのメソッドを利用する

犬クラスを拡張する (2)

メソッドをオーバーライドする際に、既存(スーパークラス側)のメソッドを利用することもできます。予約語superを使用して super.~(~)の形でメソッド呼び出しをすることができます。

ここでは、遠吠えする犬クラス(HowlingDogクラス)を作成してみましょう。HowlingDogクラスでは、2分の 1の確率で遠吠えをするか、既存の鳴き方をします。

ソースコードは以下の通り。

M204/Dog.java

/**
 * 犬を表すクラスです。
 */
public class Dog {
	/**
	 * 名前。
	 */
	protected String name;
	/**
	 * 鳴く回数。
	 */
	protected int cryCount;
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 */
	public Dog(String name) {
		this(name, 1);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 * @param cryCount 鳴く回数
	 */
	public Dog(String name, int cryCount) {
		this.setName(name);
		this.setCryCount(cryCount);
	}
	
	/**
	 * 名前を設定します。
	 * @param name 名前
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * 鳴く回数を設定します。
	 * @param cryCount 鳴く回数
	 */
	public void setCryCount(int cryCount) {
		this.cryCount = cryCount;
	}
	
	/**
	 * 鳴きます。
	 */
	public void cry() {
		System.out.print(this.name + "「");
		for(int i = 0; i < this.cryCount; i ++) {
			System.out.print("ワン");
		}
		System.out.println("」");
	}
	
	/**
	 * お座りします。
	 */
	public void sitDown() {
		System.out.println(this.name + "は座りました。");
	}
}

M204/HowlingDog.java

/**
 * 遠吠えする犬を表すクラスです。
 */
public class HowlingDog extends Dog {
	/**
	 * コンストラクタ。
	 * @param name 名前
	 */
	public HowlingDog(String name) {
		super(name);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 * @param cryCount 鳴く回数
	 */
	public HowlingDog(String name, int cryCount) {
		super(name, cryCount);
	}
	
	@Override
	public void cry() {
		int random = (int)(Math.random() * 2);
		
		if(random == 0) {
			// 既存の鳴き声で鳴く
			super.cry();
		} else {
			// 遠吠えする
			System.out.print(this.name + "「ワオ");
			for(int i = 0; i < this.cryCount; i ++) {
				System.out.print("ー");
			}
			System.out.println("ン」");
		}
	}
}

M204/M204.java

/**
 * Dogクラスと HowlingDogクラスを利用します。
 */
public class M204 {
	
	/**
	 * メインメソッド。
	 * @param args 引数
	 */
	public static void main(String[] args) {
		// インスタンスを作成
		Dog dog1 = new Dog("ポチ");
		Dog dog2 = new Dog("クロ", 5);
		HowlingDog howlingDog1 = new HowlingDog("ラッキー");
		HowlingDog howlingDog2 = new HowlingDog("ハチ", 10);
		
		// 犬のインスタンスメソッドを実行
		dog1.cry();
		dog1.sitDown();
		dog2.cry();
		
		// 遠吠えする犬のインスタンスメソッドを実行
		howlingDog1.cry();
		howlingDog1.cry();
		howlingDog1.sitDown();
		howlingDog2.cry();
		howlingDog2.cry();
		howlingDog2.sitDown();
	}
}

実行結果の例は以下の通り。

ポチ「ワン」
ポチは座りました。
クロ「ワンワンワンワンワン」
ラッキー「ワオーン」
ラッキー「ワン」
ラッキーは座りました。
ハチ「ワオーーーーーーーーーーン」
ハチ「ワンワンワンワンワンワンワンワンワンワン」
ハチは座りました。
ポチ「ワン」
ポチは座りました。
クロ「ワンワンワンワンワン」
ラッキー「ワン」
ラッキー「ワオーン」
ラッキーは座りました。
ハチ「ワンワンワンワンワンワンワンワンワンワン」
ハチ「ワンワンワンワンワンワンワンワンワンワン」
ハチは座りました。

まず、数を数えられることが大切です。Dogクラスと HowlingDogクラスについて、以下の数を正確に数えられるでしょうか。

  • Dogクラス
    • 宣言された staticなフィールド…0個
    • 宣言された staticなメソッド…0個
    • 宣言されたインスタンスフィールド…2個
    • 宣言されたインスタンスメソッド…4個
    • コンストラクタ…2種類
  • HowlingDogクラス
    • 宣言された staticなフィールド…0個
    • 宣言された staticなメソッド…0個
    • 宣言されたインスタンスフィールド…0個(Dogクラスで宣言されたものも合算すると 2個)
    • 宣言されたインスタンスメソッド…1個(Dogクラスで宣言されたものも合算すると 4個。4 + 1 = 4になるのは、1個のメソッドがオーバーライドされているため)
    • コンストラクタ…2種類

HowlingDogクラスの cryメソッドが、既存の(スーパークラスの)メソッドを呼び出す場合があることを理解できるでしょうか。

最終更新: 2013/02/23 , 公開: 2013/02/23
▲top