抽象クラスは、一見するとどんな使い道があるかわかりにくいクラスです。
そこで、一番手っ取り早いメリット「オーバーライドしなきゃいけない!」という点を見てみましょう。
抽象クラスでミスを防ごう
抽象クラスのメリットを見るために、抽象クラスを使用しなかった場合の失敗例を見てみましょう。
/**
* 抽象クラス……じゃない、普通のクラス。
*/
class NonAbstractSuper
{
/**
* 普通のメソッド。
*/
void printMyName()
{
System.out.println( "NonAbstractSuper" );
}
}
/**
* NonAbstractSuperクラスのサブクラス。
*/
class TypoSub extends NonAbstractSuper
{
/**
* オーバーライドしている……つもり。
*/
void printNyName()
{
System.out.println( "TypoSub" );
}
}
/**
* 実行用クラス。このクラスを実行してください。
*/
class TypoSubRunner
{
public static void main( String[] args )
{
// TypoSubクラスのインスタンスを1つ作ります。
// 参照型変数はNonAbstractSuperクラスです。
NonAbstractSuper refSuper = new TypoSub();
// オーバーライドしたメソッドを呼び出します。
refSuper.printMyName();
// 実行結果:
// NonAbstractSuper
// ……あれ?
}
}
// TypoSubRunner.java /** * 抽象クラス……じゃない、普通のクラス。 */ class NonAbstractSuper { /** * 普通のメソッド。 */ void printMyName() { System.out.println( "NonAbstractSuper" ); } } /** * NonAbstractSuperクラスのサブクラス。 */ class TypoSub extends NonAbstractSuper { /** * オーバーライドしている……つもり。 */ void printNyName() { System.out.println( "TypoSub" ); } } /** * 実行用クラス。このクラスを実行してください。 */ class TypoSubRunner { public static void main( String[] args ) { // TypoSubクラスのインスタンスを1つ作ります。 // 参照型変数はNonAbstractSuperクラスです。 NonAbstractSuper refSuper = new TypoSub(); // オーバーライドしたメソッドを呼び出します。 refSuper.printMyName(); // 実行結果: // NonAbstractSuper // ……あれ? } }
まず、NonAbstractSuperクラスがあります。
これは前ページの抽象クラスに似ていますが、普通のクラスです。
* 抽象クラス……じゃない、普通のクラス。
*/
class NonAbstractSuper
{
/**
* 普通のメソッド。
*/
void printMyName()
{
System.out.println( "NonAbstractSuper" );
}
}
/** * 抽象クラス……じゃない、普通のクラス。 */ class NonAbstractSuper { /** * 普通のメソッド。 */ void printMyName() { System.out.println( "NonAbstractSuper" ); } }
抽象クラスじゃないので、抽象メソッドは作れません。
なので、printMyName()メソッドも普通のメソッドです。
そのサブクラス、TypoSubクラスも用意します。
/**
* NonAbstractSuperクラスのサブクラス。
*/
class TypoSub extends NonAbstractSuper
{
/**
* オーバーライドしている……つもり。
*/
void printNyName()
{
System.out.println( "TypoSub" );
}
}
/** * NonAbstractSuperクラスのサブクラス。 */ class TypoSub extends NonAbstractSuper { /** * オーバーライドしている……つもり。 */ void printNyName() { System.out.println( "TypoSub" ); } }
TypoSubクラスは、NonAbstractSuperクラスのサブクラスです。
このクラスでは、printMyName()メソッドをオーバーライドしています。
……そのつもりでした。
でもオーバーライドできていません。
よく見てください。メソッド名が「printNyName」となっています。スーパークラスのprintMyName()メソッドと、メソッド名が違います!
こうなるとオーバーライドされません。
// 参照型変数はNonAbstractSuperクラスです。
NonAbstractSuper refSuper = new TypoSub();
// TypoSubクラスのインスタンスを1つ作ります。 // 参照型変数はNonAbstractSuperクラスです。 NonAbstractSuper refSuper = new TypoSub();
このようにインスタンスを作っても、printMyName()メソッドとprintNyName()メソッドは全然関係ないメソッドなので、オーバーライドされず、ひもも付きません。
当然、スーパークラスのprintMyName()メソッドを呼び出そうとすると、そのままprintMyName()メソッドが呼び出されます。
refSuper.printMyName();
// 実行結果:
// NonAbstractSuper
// ……あれ?
// オーバーライドしたメソッドを呼び出します。 refSuper.printMyName(); // 実行結果: // NonAbstractSuper // ……あれ?
実行結果から分かるとおり、サブクラスのprintNyName()メソッドは呼ばれるわけがなく、スーパークラスのprintMyName()メソッドが呼び出されます。
このように、オーバーライドする際にメソッド名を間違えることで、オーバーライドに失敗してしまいます。
その他に、引数を変えた場合にもオーバーライドされません。
このように、ちょっとしたミスでオーバーライドできず、しかも実行しなければミスしたことに気付きません。
このような問題を回避するためには、抽象クラスを使うことです。
前ページの例のように、クラスを抽象クラスにして、printMyName()メソッドを抽象メソッドにすると、そのメソッドはサブクラスで必ずオーバーライドする必要があります。
もし今回の例のようにオーバーライドできていないとコンパイルエラーになるので一発で分かります。
このメリットを活用するために、抽象クラスを使ってみましょう。
とは言っても
ただし、今ここで言ったメリットは「一応のメリット」です。
たとえば「6.2 3段以上オーバーライドしたら?」のように、数段に渡ってオーバーライドしている場合には、今回のメリットは得られません。たとえHasOneMethodSuper3クラスのprintMyName()メソッドを抽象クラスにしても、SubOfSubクラスでprintMyName()メソッドをタイプミスしてしまったら、それを防ぐことはできません。
また、単に「オーバーライドのミスをなくしたい」という場合には、「Overrideアノテーション」というのものがあるのでそちらを利用した方がいいでしょう。
じゃあなんのために……と言われるとちょっと難しいです。
一応「本来の目的」があるのですが、その目的のためには後で説明する「インタフェース」の方が便利なので、実際に抽象クラスを使う場面は少ないでしょう。
正直、「まぁこういう機能がある」というくらいに憶えておいて、後述のインタフェースを理解するための道具としてください。