抽象クラス
抽象クラスとは
応用編第一日目で説明したとおり、javaでは、あるクラスを継承したクラスを作ることができました。ここでは、継承の概念をさらに発展させた、抽象クラスという概念について説明します。
類似する概念の集約
サンプルプログラム
抽象クラスについて説明する前に、まずは以下のクラスを入力してみてください。
SampleClassEx301.javapackage 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(); } }
package exday3; // カラスクラス public class Crow1{ private String name="カラス"; // 名前を取得 public String getName(){ return name; } // カラスがなく public void sing(){ System.out.println("カーカー"); } }
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.javapackage 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(); } }
package exday3; // カラスクラス public class Crow2 extends Bird{ // コンストラクタ(引数なし) public Crow2(){ super("カラス"); // Birdクラスの引数つきコンストラクタを呼び出す } // カラスがなく public void sing(){ System.out.println("カーカー"); } }
package exday3; // すずめクラス public class Sparrow2 extends Bird { // コンストラクタ(引数なし) public Sparrow2(){ super("すずめ"); // Birdクラスの引数つきコンストラクタを呼び出す } // 雀が鳴く public void sing(){ System.out.println("チュンチュン"); } }
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.「鳥」は、あくまでも抽象的な概念
…
}
しかし、鳥には「カラス」や「すずめ」といったような具体的な名前があり、また「鳴く」という動作をすることも共通です。そこで、Birdクラスには、そういったメンバが付加されています。
抽象メソッド
また、Bird.javaの14行目を見てください。ここには、sing()というメソッドが定義されていますが、処理が実装されていません。その上、先頭にクラスと同様、abstractという修飾子が先頭についています。このようなメソッドを、抽象メソッドと言います。
抽象メソッドはの実装は、抽象クラスを継承したサブクラスで実装されます。たとえば、Crow2.javaの14行目、およびSparrow2.javaクラスの14行目で、sing()クラスが実装されています。それぞれ動作は違いますが、どちらも「カラス」および「すずめ」が鳴く動作になっています。
抽象メソッドの定義このように、抽象クラスとは、抽象メソッドを持ち、動作をサブクラスで行われるようなクラスのことを言います。すでに述べた通り、抽象クラスがインスタンスを持つことはできません。
抽象クラスのインスタンスを生成することはできない親クラスのコンストラクタの呼び出し
ところで、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.