抽象クラスを作る

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


抽象クラスの基礎

抽象クラスとは

抽象クラスをひとことで言えば、「abstractなクラス」になります。

抽象クラスは、継承されることを前提としたクラスで、自身のインスタンスを生成することができません。抽象クラスでは、メソッド名だけを決めて、そのメソッドの実装についてはサブクラス側に委ねる…という形をとります。

予約語 abstract

クラスを抽象クラスとするための予約語はabstractです。予約語 abstractは、クラス名またはメソッド名の前に使用します。言い換えれば、引数やフィールド、ローカル変数に対して使用することはありません。abstractについては、以下のようなルールがあります。

  • クラス名の前に abstractを使用した場合、そのクラスは抽象クラスになる。
  • メソッド名の前に abstractを使用した場合、そのメソッドは抽象メソッドになる。このとき、
    • メソッドの中身を記述してはいけないメソッド名(~);というようにセミコロンで終える。
    • 抽象メソッドを含むクラスは抽象クラスになる。そのため、クラス名の前にも abstractを記述しなければならない
  • 抽象クラスを継承したクラスは、以下のどちらかになる。
    • 抽象メソッドをすべて実装したならば、普通のクラスになる。
    • ひとつでも抽象メソッドを実装しなかったならば、自身も抽象クラスになる。

抽象メソッド cryを持つ抽象クラス Animalの宣言は以下のような感じになります。

public abstract class Animal {
	public abstract void cry();
}

抽象クラスを継承したクラスは、以下のような感じになります。

public class Cat extends Animal {
	@Override
	public void cry() {
		; // 処理
	}
}

上記の例では、Animalクラスを継承した Catクラスは cryメソッドを実装し、普通のクラスになっています。

抽象メソッドを実装しなかった場合には、自身も抽象クラスになります。

public abstract class Bird extends Animal {
	
}

上記の例では、Animalクラスを継承した Birdクラスは cryメソッドを実装せず、抽象クラスになっています。

インスタンスは作れない

抽象クラスは、インスタンスを作れません。以下のような記述はビルドエラーになります。

		Animal animal = new Animal(); // ビルドエラー

抽象クラスは、メソッドの名前だけを決定し、その実装はサブクラスに委ねます。つまり、抽象クラスは、一般的にメソッドの中身がまだ決まっていないということになります。したがって、インスタンスを作成することはできません。

なお、Mathクラスなどもインスタンスを作成することはできませんでした。これは抽象クラスとはまったく関係がありません。Mathクラスでは、アクセス修飾子によって利用できるコンストラクタを見かけゼロにしたことで、インスタンスを生成できないようにしています。アクセス修飾子については、後の章で解説します。

参照型変数は作れる

インスタンスは作れない。けれども、参照型変数は作成できます。

		Animal animal1 = null;
		Animal animal2 = new Cat();

この例では、Animal型の参照型変数 animal1, animal2を作成しています。animal1はどのインスタンスも参照しておらず(null)、animal2は Cat型インスタンスを参照しています。

このような組み合わせによって、多態性が実現できるようになります。多態性については、次の節で解説します。

抽象クラスを作る (1)

目的

抽象クラスの利用法を確認します。

方針

先の節で作成した犬(Dog)クラスと猫(Cat)クラスは、それぞれスーパークラスが Obectクラスでした。ここでは、Animalという抽象クラスを作り、Dogクラスと Catクラスがそれぞれ Animalクラスを継承するように改良したいと思います。Animalクラスには抽象メソッド cryを宣言し、Dogクラスと Catクラスではそれを実装することとします。

先の節で作成した Dogクラスの cryメソッドと、Catクラスの cryメソッドは、たまたまメソッド名が一致しているだけでした。今回、Animalという抽象クラスに cryという抽象メソッドを宣言することで、意図して一致させているということになります。

イメージとしては以下のような感じです。このイメージの中に本来はメソッドも記述すべきですが、省略しています。

抽象クラスを作る (1)

実装

Animalクラスと M501クラスは新規作成、Dogクラスと WiseDogクラスは M301プロジェクトのものを改良、Catクラスと WiseCatクラスは M202プロジェクトのものを改良しています。全体のソースコードがとても長いですが、クラスの相関関係を理解するためにはある程度の数のクラスが必要になります。

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

M501/Animal.java

/**
 * 動物を表す抽象クラスです。
 */
public abstract class Animal {
	
	/**
	 * 鳴きます。
	 */
	public abstract void cry();
}

M501/Dog.java

/**
 * 犬を表すクラスです。
 */
public class Dog extends Animal {
	/**
	 * 名前。
	 */
	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;
	}
	
	@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 sitDown() {
		System.out.println(this.name + "は座りました。");
	}
	
	@Override
	public String toString() {
		return "[Dog] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M501/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 + "はお手をしました。");
	}
	
	@Override
	public String toString() {
		return "[WiseDog] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M501/Cat.java

/**
 * 猫を表すクラスです。
 */
public class Cat extends Animal {
	/**
	 * 名前。
	 */
	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;
	}
	
	@Override
	public void cry() {
		System.out.print(this.name + "「");
		for(int i = 0; i < this.cryCount; i ++) {
			System.out.print("ニャー");
		}
		System.out.println("」");
	}
	
	@Override
	public String toString() {
		return "[Cat] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M501/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 + "は横になりました。");
	}
	
	@Override
	public String toString() {
		return "[WiseCat] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M501/M501.java

/**
 * 抽象クラスを確認します。
 */
public class M501 {
	
	/**
	 * メインメソッド。
	 * @param args 引数
	 */
	public static void main(String[] args) {
		WiseDog wiseDog = new WiseDog("ハチ" , 10);
		Animal animal1 = wiseDog;
		WiseCat wiseCat = new WiseCat("タマ", 5);
		Animal animal2 = wiseCat;
		
		wiseDog.cry();
		wiseDog.touch();
		animal1.cry();
		System.out.println("変数 animal1の参照先インスタンスの内容は " + animal1);
		
		wiseCat.cry();
		wiseCat.lie();
		animal2.cry();
		System.out.println("変数 animal2の参照先インスタンスの内容は " + animal2);
	}
}

実行結果は以下の通り。

ハチ「キャンキャンキャンキャンキャンキャンキャンキャンキャンキャン」
ハチはお手をしました。
ハチ「キャンキャンキャンキャンキャンキャンキャンキャンキャンキャン」
変数 animal1の参照先インスタンスの内容は [WiseDog] name:ハチ, cryCount:10
タマ「ニャーニャーニャーニャーニャー」
タマは横になりました。
タマ「ニャーニャーニャーニャーニャー」
変数 animal2の参照先インスタンスの内容は [WiseCat] name:タマ, cryCount:5

メインメソッドで WiseDog型インスタンスを 1個、WiseCat型インスタンスを 1個作成しています。animal1.cry();animal2.cry();が上手く動作できていることが分かるでしょうか。

逆に言えば、animal1.touch();animal2.lie();はビルドエラーになります。Animalクラスでは touchメソッドや lieメソッドが宣言されていないため、実際のインスタンスの型が何であれビルドエラーになります。

抽象クラスを作る (2)

目的

抽象クラスに、フィールドや(抽象メソッドではない)メソッドが宣言できることを確認します。

方針

インスタンスフィールド name, cryCountを Animalクラスで宣言するように改良します。これらのアクセス修飾子は protectedとします。アクセス修飾子の詳細については、後の章で解説します。

これまでに作成した Dogクラスのインスタンスフィールド name, cryCountと、Catクラスのインスタンスフィールド name, cryCountメソッドは、たまたまメソッド名が一致しているだけでした。今回、Animalという抽象クラスにインスタンスメソッド name, cryCountを宣言し、関連するメソッドも同様に宣言することで、意図して一致させているということになります。

イメージとしては以下のような感じです。このイメージの中に本来はメソッドも記述すべきですが、省略しています。

抽象クラスを作る (2)

実装

M502クラスは新規作成、Animalクラス、Dogクラス、Catクラスは M501プロジェクトのものを改良、WiseDogクラス、WiseCatクラスは M501プロジェクトのものをそのまま利用しています。

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

M502/Animal.java

/**
 * 動物を表す抽象クラスです。
 */
public abstract class Animal {
	/**
	 * 名前。
	 */
	protected String name;
	/**
	 * 鳴く回数。
	 */
	protected int cryCount;
	
	/**
	 * 名前を設定します。
	 * @param name 名前
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * 鳴く回数を設定します。
	 * @param cryCount 鳴く回数
	 */
	public void setCryCount(int cryCount) {
		this.cryCount = cryCount;
	}
	
	/**
	 * 鳴きます。
	 */
	public abstract void cry();
}

M502/Dog.java

/**
 * 犬を表すクラスです。
 */
public class Dog extends Animal {
	/**
	 * コンストラクタ。
	 * @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);
	}
	
	@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 sitDown() {
		System.out.println(this.name + "は座りました。");
	}
	
	@Override
	public String toString() {
		return "[Dog] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M502/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 + "はお手をしました。");
	}
	
	@Override
	public String toString() {
		return "[WiseDog] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M502/Cat.java

/**
 * 猫を表すクラスです。
 */
public class Cat extends Animal {
	/**
	 * コンストラクタ。
	 */
	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);
	}
	
	@Override
	public void cry() {
		System.out.print(this.name + "「");
		for(int i = 0; i < this.cryCount; i ++) {
			System.out.print("ニャー");
		}
		System.out.println("」");
	}
	
	@Override
	public String toString() {
		return "[Cat] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M502/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 + "は横になりました。");
	}
	
	@Override
	public String toString() {
		return "[WiseCat] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M502/M502.java

/**
 * 抽象クラスを確認します。
 */
public class M502 {
	
	/**
	 * メインメソッド。
	 * @param args 引数
	 */
	public static void main(String[] args) {
		WiseDog wiseDog = new WiseDog("ハチ" , 10);
		Animal animal1 = wiseDog;
		WiseCat wiseCat = new WiseCat("タマ", 5);
		Animal animal2 = wiseCat;
		
		wiseDog.cry();
		wiseDog.touch();
		animal1.cry();
		System.out.println("変数 animal1の参照先インスタンスの内容は " + animal1);
		
		wiseCat.cry();
		wiseCat.lie();
		animal2.cry();
		System.out.println("変数 animal2の参照先インスタンスの内容は " + animal2);
	}
}

実行結果は以下の通り。

ハチ「キャンキャンキャンキャンキャンキャンキャンキャンキャンキャン」
ハチはお手をしました。
ハチ「キャンキャンキャンキャンキャンキャンキャンキャンキャンキャン」
変数 animal1の参照先インスタンスの内容は [WiseDog] name:ハチ, cryCount:10
タマ「ニャーニャーニャーニャーニャー」
タマは横になりました。
タマ「ニャーニャーニャーニャーニャー」
変数 animal2の参照先インスタンスの内容は [WiseCat] name:タマ, cryCount:5

実行結果は M501プロジェクトと同じになります。

スーパークラス側(Animalクラス)で宣言されたインスタンスフィールドをサブクラス側(Dogクラスや Catクラス)で利用する際には、this.~と記述します。super.~ではないことに注意してください。サブクラス側がこれらのインスタンスフィールドを継承しているため、this.~と記述して問題ないのです。逆に言えば、フィールドに対してsuper.~と記述するようなことはありません(メソッドの場合にはあります。フィールドの場合にはありません)。

抽象クラスを作る (3)

目的

抽象クラスを継承したクラスが、抽象メソッドを実装しなかった場合には、そのクラスも抽象クラスとなることを確認します。

方針

Animalクラスを継承した抽象クラス Birdを作成し、flyメソッドを宣言します。また、Birdクラスを継承した Crowクラスを作成します。2段階にすることで、今後、同じ鳥類である Pigeon(鳩)クラスなどを作成しやすいようにします。

イメージとしては以下のような感じです。このイメージの中に本来はメソッドも記述すべきですが、省略しています。

抽象クラスを作る (3)

実装

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

M503/Animal.java

/**
 * 動物を表す抽象クラスです。
 */
public abstract class Animal {
	/**
	 * 名前。
	 */
	protected String name;
	/**
	 * 鳴く回数。
	 */
	protected int cryCount;
	
	/**
	 * 名前を設定します。
	 * @param name 名前
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * 鳴く回数を設定します。
	 * @param cryCount 鳴く回数
	 */
	public void setCryCount(int cryCount) {
		this.cryCount = cryCount;
	}
	
	/**
	 * 鳴きます。
	 */
	public abstract void cry();
}

M503/Bird.java

/**
 * 鳥を表す抽象クラスです。
 */
public abstract class Bird extends Animal {
	
	/**
	 * 飛びます。
	 */
	public void fly() {
		System.out.println(this.name + "は飛びました。");
	}
}

M503/Crow.java

/**
 * 犬を表すクラスです。
 */
public class Crow extends Bird {
	/**
	 * コンストラクタ。
	 */
	public Crow() {
		this(1);
	}
	
	/**
	 * コンストラクタ。
	 * @param cryCount 鳴く回数
	 */
	public Crow(int cryCount) {
		this.setName("名無しのカラス");
		this.setCryCount(cryCount);
	}
	
	@Override
	public void cry() {
		System.out.print(this.name + "「");
		for(int i = 0; i < this.cryCount; i ++) {
			System.out.print("カー");
		}
		System.out.println("」");
	}
	
	@Override
	public String toString() {
		return "[Crow] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M503/M503.java

/**
 * 抽象クラスを確認します。
 */
public class M503 {
	
	/**
	 * メインメソッド。
	 * @param args 引数
	 */
	public static void main(String[] args) {
		Crow crow = new Crow(4);
		Bird bird = crow;
		Animal animal = crow;
		crow.cry();
		crow.fly();
		
		bird.fly();
		System.out.println("変数 animalの参照先インスタンスの内容は " + animal);
	}
}

実行結果は以下の通り。

名無しのカラス「カーカーカーカー」
名無しのカラスは飛びました。
名無しのカラスは飛びました。
変数 animalの参照先インスタンスの内容は [Crow] name:名無しのカラス, cryCount:4

Birdクラスは cryメソッドを実装していないので抽象クラスになります。Crowクラスは cryメソッドを実装したので普通のクラスになりました。今後、例えば鳩(Pigeon)クラスを作成するような場合には、Birdクラスを継承して作成すれば良し、ということになります。

なお、カラスに名前をつける人は少ないと思うので、常に「名無しのカラス」となるようにコンストラクタを作成しました。

オーバーライド・実装とアノテーション

アノテーションのひとつ「@Override」は、以下の 3つの場合に使用することができます。

  1. 既存のメソッドをオーバーライドする(以前に登場しました)。
  2. 抽象クラスの抽象メソッドを実装する(この節で登場しました)。
  3. インターフェイスの(抽象)メソッドを実装する(後の章で解説します)。

言葉遣いとしては、「1」をオーバーライド、「2」および「3」を実装(インプリメント)と使い分けます。本ウェブサイトでも使い分けています。ただ、アノテーションの記述としては、「1」~「3」まで、すべて「@Override」と記述します。

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