この章で説明する「抽象クラス」は、実際にはあまり使わない機能です。
ただ、その次の章で説明する「インタフェース」を理解するうえでの手助けになるので、とりあえずかじっておきましょう。
抽象クラスを作ってみる
ここまでの説明で、オーバーライドとアップキャストを使うことでポリモーフィズムができることはわかったと思います。
この「ポリモーフィズム」を強制させる機能があります。
それがこれから説明する「抽象クラス」というものです。
まずはこの「抽象クラス」というものを作ってみましょう。
/**
* 抽象クラス。
*/
abstract class AbstractSuper
{
/**
* 抽象メソッド。
*/
abstract void printMyName();
}
/**
* AbstractSuperクラスのサブクラス。
*/
class ConcreteSub extends AbstractSuper
{
/**
* 抽象メソッドをオーバーライドします。
*/
void printMyName()
{
System.out.println( "ConcreteSub" );
}
}
/**
* 実行用クラス。このクラスを実行してください。
*/
class ConcreteSubRunner
{
public static void main( String[] args )
{
// ConcreteSubクラスのインスタンスを1つ作ります。
// 参照型変数はAbstractSuperクラスです。
AbstractSuper refSuper = new ConcreteSub();
// オーバーライドしたメソッドを呼び出します。
refSuper.printMyName();
// 実行結果:
// ConcreteSub
}
}
// ConcreteSubRunner.java /** * 抽象クラス。 */ abstract class AbstractSuper { /** * 抽象メソッド。 */ abstract void printMyName(); } /** * AbstractSuperクラスのサブクラス。 */ class ConcreteSub extends AbstractSuper { /** * 抽象メソッドをオーバーライドします。 */ void printMyName() { System.out.println( "ConcreteSub" ); } } /** * 実行用クラス。このクラスを実行してください。 */ class ConcreteSubRunner { public static void main( String[] args ) { // ConcreteSubクラスのインスタンスを1つ作ります。 // 参照型変数はAbstractSuperクラスです。 AbstractSuper refSuper = new ConcreteSub(); // オーバーライドしたメソッドを呼び出します。 refSuper.printMyName(); // 実行結果: // ConcreteSub } }
まず、AbstractSuperクラスが抽象クラスです。
* 抽象クラス。
*/
abstract class AbstractSuper
{
/**
* 抽象メソッド。
*/
abstract void printMyName();
}
/** * 抽象クラス。 */ abstract class AbstractSuper { /** * 抽象メソッド。 */ abstract void printMyName(); }
「class クラス名」の前に「abstract」(アブストラクト)を付けると、そのクラスは「抽象クラス」になります。
抽象クラスは普通のクラスと違い、「抽象メソッド」を作ることができます。
printMyName()メソッドがその「抽象メソッド」です。
メソッドの前に「abstract」を付けるとそのメソッドは「抽象メソッド」になります。
抽象メソッドは「中身がないメソッド」です。
printMyName()メソッドには、普通のメソッドにはある「{ ~ }」がなく、代わりに「;」で閉じられています。つまりメソッドの中身がないんです。
さて、ではこの「中身がないメソッド」にどんなメリットがあるのでしょう。
それは「強制的にオーバーライドさせられる」という点です。
AbstractSuperクラスのサブクラス、ConcreteSubクラスを見てみましょう。
* AbstractSuperクラスのサブクラス。
*/
class ConcreteSub extends AbstractSuper
{
/**
* 抽象メソッドをオーバーライドします。
*/
void printMyName()
{
System.out.println( "ConcreteSub" );
}
}
/** * AbstractSuperクラスのサブクラス。 */ class ConcreteSub extends AbstractSuper { /** * 抽象メソッドをオーバーライドします。 */ void printMyName() { System.out.println( "ConcreteSub" ); } }
ConcreteSubクラスは、抽象クラスであるAbstractSuperクラスのサブクラスです。
抽象クラスからの継承は普通のクラスと同じようにできますし、このConcreteSubクラスそのものは普通のクラスと違いはありません。
このクラスでは、printMyName()メソッドをオーバーライドしています。
実はこのメソッド、ないとコンパイルエラーになります。
抽象メソッドは「中身のないメソッド」なので、抽象メソッドを持つクラスはそのままでは使えません。使うためには必ずオーバーライドをする必要があります。まぁ「中身のないメソッドに中身を入れる」ようなものです。
この状態で、サブクラスのインスタンスを作ってみます。
// 参照型変数はAbstractSuperクラスです。
AbstractSuper refSuper = new ConcreteSub();
// ConcreteSubクラスのインスタンスを1つ作ります。 // 参照型変数はAbstractSuperクラスです。 AbstractSuper refSuper = new ConcreteSub();
このConcreteSubクラスのインスタンスを作ると、抽象メソッドからオーバーライドしたメソッドへとひもが付きます。
サブクラスのインスタンスの中にスーパークラスのインスタンスが入っている形はこれまで見てきた例と同じです。
ただ、スーパークラスの中のprintMyName()メソッドは「抽象メソッド」なので中身がなく、サブクラスにあるオーバーライドしたメソッドにひもが付きます。
そのため、スーパークラスの「中身がない」printMyName()メソッドを呼び出そうとしたとしても、自動的にサブクラスでオーバーライドした方が呼び出されます。
refSuper.printMyName();
// 実行結果:
// ConcreteSub
// オーバーライドしたメソッドを呼び出します。 refSuper.printMyName(); // 実行結果: // ConcreteSub
refSuper変数はAbstractSuperクラスの参照型変数なので、そのprintMyName()メソッドを呼び出すと「中身のないメソッドを呼び出そうとするんじゃ」と思われるかもしれませんが、このメソッドはConcreteSubクラスのprintMyName()メソッドにひもが付いていますから、そのConcreteSubクラスのメソッドが呼び出されます。
というか、ここで「オーバーライドしたメソッドが呼び出される」のは、これまで説明してきたポリモーフィズムの仕組みと同じです。
参照型変数の型に関係なくインスタンスを作成したときにオーバーライドしたメソッドが呼び出される、ただそれだけのことです。
抽象メソッドで中身がないとかそういうことはあまり関係ない、と考えた方がいいかもしれません。
逆から考えよう
「抽象クラス」とか「抽象メソッド」とか「abstract」とかいっぱい出てきてちょっと混乱するかもしれません。
単純に「メソッドを必ずオーバーライドさせたい!」→「じゃあそのメソッドにabstractを付けよう」→「そしたらクラスにもabstract付けなきゃ」と、目的から逆に考えた方が分かりやすいかもしれません。
あ、あと、抽象クラスのメソッド全部を抽象メソッドにする必要はありません。
抽象メソッドにしたいメソッドだけabstractを付けて中身を取っ払えばいいだけです。
普通のメソッドはそのまま残しておけばいいので、スーパークラスの中で「このメソッドは必ずオーバーライドさせたい!」と思ったメソッドがあったら気軽に抽象メソッドにしちゃいましょう。