抽象クラス

抽象クラスとは

応用編第一日目で説明したとおり、javaでは、あるクラスを継承したクラスを作ることができました。ここでは、継承の概念をさらに発展させた、抽象クラスという概念について説明します。

類似する概念の集約

サンプルプログラム

抽象クラスについて説明する前に、まずは以下のクラスを入力してみてください。

SampleClassEx301.java
package exday3;

public class SampleEx301 {
	public static void main(String args[]){
		Crow1 c = new Crow1();			//	カラスクラス
		Sparrow1 s = new Sparrow1();	//	すずめクラス
		//	カラスがなく
		System.out.print(c.getName()+" : ");
		c.sing();
		//	雀がなく
		System.out.print(s.getName()+" : ");
		s.sing();
	}
}
Crow1.java
package exday3;

//	カラスクラス
public class Crow1{
	private String name="カラス";
	// 名前を取得
	public String getName(){ return name; }
	//	カラスがなく
	public void sing(){ System.out.println("カーカー"); }
}
Sparrow1.java
package exday3;

//	すずめクラス
public class Sparrow1 {
	private String name="すずめ";
	// 名前を取得
	public String getName(){ return name; }
	// 雀が鳴く
	public void sing(){ System.out.println("チュンチュン"); }
}
実行結果
カラス : カーカー
すずめ : チュンチュン

このプログラムでは、二つのクラスCrow1、Sparrow1というクラスを生成し、それぞれに共通するsing()というメソッドを呼び出しています。Crowとは英語で、「カラス」、Sparrowは「すずめ」を意味する言葉です。

どちらも、「鳥」であり、共通する特徴である、「鳴く(sing)」という行為を行うことができます。このように、類似したクラスが、同一名のメンバを持つことがしばしばあります。

また、getName()メソッドは、どちらのクラスにも共通であり、中身も変わりません。つまり、これらのクラスは、共通する処理を行う部分と、異なる処理を行う部分があります。このように、部分的な類似する処理があり、同一名の異なる処理があるときに有用になるのが、抽象(ちゅうしょう)クラスです。

抽象クラス

サンプルプログラム

では、サンプルプログラムを用いると、この処理はどのようになるのでしょう?それが、以下のサンプルです。まずは、入力して実行してみてください。

SampleClassEx202.java
package exday3;

public class SampleEx302 {
	public static void main(String args[]){
		Crow2 c = new Crow2();
		Sparrow2 s = new Sparrow2();
		//	カラスがなく
		System.out.print(c.getName()+" : ");
		c.sing();
		//	雀がなく
		System.out.print(s.getName()+" : ");
		s.sing();
	}
}
Crow2.java
package exday3;

//	カラスクラス
public class Crow2 extends Bird{
	//	コンストラクタ(引数なし)
	public Crow2(){
		super("カラス");	//	Birdクラスの引数つきコンストラクタを呼び出す
	}
	//	カラスがなく
	public void sing(){ System.out.println("カーカー"); }
}
Sparrow2.java
package exday3;

//	すずめクラス
public class Sparrow2 extends Bird {
	//	コンストラクタ(引数なし)
	public Sparrow2(){
		super("すずめ");	//	Birdクラスの引数つきコンストラクタを呼び出す
	}
	// 雀が鳴く
	public void sing(){ System.out.println("チュンチュン"); }
}
Bird.java
package exday3;

//	抽象クラス(鳥)
public abstract class Bird {
	//	名前フィールド
	private String name;
	//	引数つきコンストラクタ
	Bird(String name){
		this.name = name;
	}
	//	名前の取得
	public String getName(){ return name; }
	//	鳴く(抽象メソッド)
	abstract void sing();
}

実行結果は同じなので、省略します。このクラスの共通点を集約して作った親クラスが、Birdクラスです。このクラスの定義の前には、abstractという修飾子が用いられていますが、これが、このクラスが抽象クラスであることを意味します。

抽象クラスとは、それ自身では、インスタンスを生成しないクラスのことを言います。この例でいくと、「カラス」や「すずめ」という鳥は存在しますが、「鳥」という名前の鳥は存在しません。つまり、「鳥」というのは抽象的な概念であり、実在しません。(図2-1.参照)

図3-1.「鳥」は、あくまでも抽象的な概念

抽象クラスのイメージ
抽象クラスの定義
abstract class (クラス名){
    …
}

しかし、鳥には「カラス」や「すずめ」といったような具体的な名前があり、また「鳴く」という動作をすることも共通です。そこで、Birdクラスには、そういったメンバが付加されています。

抽象メソッド

また、Bird.javaの14行目を見てください。ここには、sing()というメソッドが定義されていますが、処理が実装されていません。その上、先頭にクラスと同様、abstractという修飾子が先頭についています。このようなメソッドを、抽象メソッドと言います。

抽象メソッドはの実装は、抽象クラスを継承したサブクラスで実装されます。たとえば、Crow2.javaの14行目、およびSparrow2.javaクラスの14行目で、sing()クラスが実装されています。それぞれ動作は違いますが、どちらも「カラス」および「すずめ」が鳴く動作になっています。

抽象メソッドの定義
abstract (戻り値)(メソッド名)(引数);

このように、抽象クラスとは、抽象メソッドを持ち、動作をサブクラスで行われるようなクラスのことを言います。すでに述べた通り、抽象クラスがインスタンスを持つことはできません

抽象クラスのインスタンスを生成することはできない
× Bird b = new Bird("すずめ");

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

ところで、Birdクラスには、8行目から11行目で引数付きのコンストラクタが定義されていますが、このコンストラクタはどこから呼び出されるのでしょう。このコンストラクタは、サブクラスのコンストラクタから呼び出されています。

Crow2.javaの6行目、およびSparrow2.javaクラスの6行目に出ている、superを見てください。これは、親クラスのコンストラクタを呼び出す処理です。引数として、それぞれの鳥の名前を与えていますが、これを引数として、Birdクラスのコンストラクタが呼び出されます。これにより、親クラスのフィールドnameに値が設定されます。(図2-2.参照)

図3-2.superによる、親クラスのコンストラクタの呼び出し

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

抽象クラスを用いることのメリット

クラスの抽象化

では、抽象クラスを用いることのメリットは何なのでしょう?このサンプルの場合、一見プログラムが却って長くなり、面倒な感じがします。しかし、「カラス」や「すずめ」のほかに、「にわとり」や「つばめ」などといった、新しい「鳥」のクラスを作ろうとした場合、どうでしょうか?

抽象クラスでは、あらかじめ「鳥」全体の共通の処理は実装されていますから、わざわざ新たに同じ処理を何度も記述する必要がありません。例えば、各クラスでは、それぞれの鳥に独特の特徴を記述すればよいのです。

抽象クラスとしてインスタンスを持つ

SampleEx203.java

package exday3;

public class SampleEx203 {
	public static void main(String args[]){
		Bird b[] = new Bird[2];		//	Birdクラスの変数の配列を生成
		b[0] = new Crow2();			//	b[0]に、Crow2クラスのインスタンスを生成
		b[1] = new Sparrow2();		//	b[1]に、Sparrow2クラスのインスタンスを生成
		for(int i = 0; i < b.length ; i++){
			System.out.print(b[i].getName()+" : ");
			b[i].sing();
		}
	}
}
実行結果

実行結果は、SampleEx302.javaと同じなので省略します。

Birdクラスとしてデータを集約

プログラムの5行目で、Birdクラスの配列を生成しています。6~7行目でそのインスタンスを生成しています。抽象クラスはインスタンスを生成することはできませんが、このように変数として値を保持することは可能です。ただし、そのインスタンスは、そのクラスを継承したサブクラスに限られています。

そのため、b[0]、b[1]に、Crow2、Sparrow2クラスのインスタンスを生成し、代入することが可能なのです。これにより、このクラスの各処理が抽象化されるため、for文のループで同様の処理を行うことができます。前述のように、抽象クラスのサブクラスが多数存在する場合、この方法は大変便利です。(図2-3.参照)

図3-3.Birdクラスから、インスタンスクラスのメソッドを呼び出す

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

練習問題 : 問題3.