【本サイトではGoogleアドセンス、または、アフィリエイト広告を利用しています。】
抽象クラスとは?
抽象クラスって何でしょう?
あ、あいまいなクラス?
どういうこと?
抽象という言葉には、「多くの物から共通部分を抜き出して、一般的な概念にする」という意味があります。
「一般的な概念」って言われても、、ちっともわかりませんよね。
使い方がわかればまさにその通りの説明なんですが、クラスが抽象的ってどういうことなんでしょうか?
例えば、犬や猫を思い浮かべてください。
犬や猫という種類の生き物はこの世界にいます。
でも、動物という種類の生き物はいないですよね?
動物というものは生き物の種類ではなくて、概念です。
動物というもの自体が存在して、近所を歩いたり鳴いたりはしません。
そして、犬や猫は動物の中の1つ、つまり、犬や猫にとって、動物は共通の概念になります。
なんとなく伝わってきたでしょうか?
この例における、動物こそ、抽象クラスです!
つまり、抽象クラスとは、サブクラス共通の概念を定義する為のクラスです。
プログラムでは以下のように定義します。
修飾子 abstract class クラス名 {
// フィールド
// メソッド
// 抽象メソッド
}
クラス名の前に「abstract」を入れることで抽象クラスとしての宣言ができます。
抽象クラスでは、フィールド、メソッドに加えて、
抽象メソッドと呼ばれるメソッドを定義できます。(後述します。)
また、フィールド、メソッド、抽象メソッドの定義はいずれも任意です。
通常のクラスと同様に、フィールド、メソッドのみ定義して使うこともできます。
(そうなると、抽象クラスにする意味はなくなりますが。。)
そして、抽象クラスは概念として定義するクラスなので、サブクラスに継承させる形で使います。
概念をオブジェクトとして動かすことはできない為、
抽象クラス自体をインスタンス化して使うことはできません。
つまり、抽象クラスは単独で使うことができないということになります。
通常のクラスの書き方や特徴についてまだイメージがついていないという方は
以下とあわせて読んでいただくとより理解が深まると思います。
抽象メソッドとは?
抽象クラスに続いて、抽象メソッドについてです。
抽象メソッドとは、抽象クラスに定義する仮のメソッドです。
仮のメソッドだけあって、抽象メソッドを直接呼び出して使うことはできません。
また、staticをつけてクラスメソッドにすることもできません。
抽象メソッドは以下のように定義します。
修飾子 abstract 戻り値の型 メソッド名(引数);
抽象クラスの時と同様に「abstract」を使います。
メソッド名、戻り値の型、引数のみを定義するのですが、処理はどこに書くんでしょう?
抽象メソッドの処理は、
抽象クラスを継承したサブクラス内でオーバーライドして書くことになっています。
また、Javaでは、抽象クラスを継承してサブクラスを作った際は、
抽象メソッドをオーバーライドしないとエラーになります。
整理すると、サブクラスは、抽象クラスで定義した抽象メソッドを
オーバーライドして「通常のメソッド」として実装する必要が出てくるが、
中身の処理自体は自由な実装が任されているということです。
このように、抽象クラスと抽象メソッドを使えば、
サブクラスの実装方針をルール化してプログラム上で強制することができます。
では、先ほどの例を使って、もう少し簡単に説明します。
動物は、鳴いたり歩いたりしますが、「鳴く」という動作に絞って考えてみると、
犬は「ワン!」、猫は「ニャー」と鳴きます。
このように、犬と猫は同じように鳴いたり歩いたりするわけではありません。
それぞれ特徴を持っていて、犬にしかない動作、猫にしかない動作があります。
でも「鳴く」という動作は共通して持っていますよね?
この共通の動作を表したものこそが、抽象メソッドです。
動物に鳴き方や鳴き声はありません。「鳴く」という動作のみ持っています。
これを抽象クラスに抽象メソッドとして定義しておくわけです。
そして、抽象クラス(動物)を継承したサブクラス(犬や猫)は、
抽象メソッド(鳴く動作)の処理(「ワン!」や「ニャー」と鳴くこと)を
必ず実装する必要があります。
そうすれば、「動物は鳴く」というルールが必ず守られてプログラムを実装できることになります。
抽象クラスや抽象メソッドを使うメリットは?
ここで、抽象クラスをサブクラス共通の概念として定義しておくことに何のメリットがあるのか、
もう少し掘り下げてみましょう。
複数人のチームでプログラムを書くことを考えてください。
マイクは「犬」担当、ケイトは「猫」担当、ジョニーは「馬」の
クラス作成をそれぞれ担当し実装します。
ここで、実装ルールを決めずに一斉にスタートしたらどうなるでしょうか?
「鳴く」という動作をそれぞれこんな感じで実装してきたりします。
マイク : bark()メソッド
ケイト : meows()メソッド
ジョニー : 未実装
マイク「おいおい、ジョニー、馬が鳴くことを知らないのかい?」
ジョニー「ああ、なんてこった!ヒヒーン!」
ケイト「まったく、ジョニーったら!ウフフ。」
きっとこんな会話が繰り広げられるはずです。
ちょっと楽しそうな職場、、いや、ち、違います!
同じ「鳴く」という動作に対して、メソッド名が個々に違っていたり、
メソッド自体実装していないことだって出てきます!
この3つのクラスを渡されて、あなたが使うとしたら。。
えーっと、犬が鳴く時は「bark」で、猫が「meows」だったっけ、馬は。。
お、覚えれない!使いにくいから
名前だけでも統一してくれー!
あと、馬が鳴けないんですけど!
こんな時に、依頼主のあなたが抽象クラスを先に作ってから3人にクラス作成を依頼すれば、
「鳴く」メソッドは「cry()」として、実装必須というルールをプログラム上で強制できるわけです!
このように、複数人で開発を行う際に、実装方法を統一できるところに
抽象クラスのメリットがあります。
eclipseを使って抽象クラスを新規作成する方法
eclipseを使って抽象クラスを新規作成する場合は、
上部メニューの「ファイル > 新規 > クラス」から、
クラスの新規作成画面を表示して「abstract」にチェックを入れます。
パッケージエクスプローラー内のパッケージを右クリックして、
「新規 > クラス」からでも作成できます。
その場合は、右クリックしたパッケージ名が最初から入った状態で
クラス作成画面が表示されるので便利です!
abstractの使い方(サンプルコード)
それでは、抽象クラスと抽象メソッドの使い方をサンプルコードで見ていきましょう。
「機械」を抽象クラスとして、「炊飯器」と「掃除機」をサブクラスとして実装します。
またそれらのクラスを使って処理を実行するmain処理をExampleクラスに持たせます。
サンプルコードの構成は以下とします。
まずは、抽象クラスの実装です。
「機械」共通の動きとして、電源のオンオフを抽象メソッドとして定義しました。
フィールド「名前」と、メソッド「名前確認」は、機械によって処理内容が変わることはないので
抽象クラス内に直接定義して同じ動きとして使えるようにしています。
package machine;
// 抽象クラス:機械
public abstract class Machine {
// フィールド:名前
public String name;
// メソッド:名前確認
public void introduction() {
System.out.println("今動いている機械の名前は「" + name + "」です。");
}
// 抽象メソッド:電源オン
public abstract void powerOn();
// 抽象メソッド:電源オフ
public abstract void powerOff();
}
次に、炊飯器のサブクラスを作ります。
電源オンオフのメソッドをオーバーライドして独自に処理を実装しています。
また、炊飯器の動きとして「ご飯を炊く」メソッドを定義しました。
package machine;
// クラス:炊飯器
public class RiceCooker extends Machine {
@Override
public void powerOn() {
System.out.println("ピピッ!炊飯器起動します。");
}
@Override
public void powerOff() {
System.out.println("ピピー!炊飯器終了します。\n");
}
// メソッド:ご飯を炊く
public void cook() {
System.out.println("もっちりふっくらご飯が炊きあがりました。");
}
}
引き続いて、「掃除機」のサブクラスです。
電源オンオフのメソッドをオーバーライドして独自に処理を実装しています。
また、掃除機の動きとして「ゴミを吸う」メソッドを定義しました。
package machine;
// クラス:掃除機
public class VacuumCleaner extends Machine {
@Override
public void powerOn() {
System.out.println("テテテッ!掃除機起動します。");
}
@Override
public void powerOff() {
System.out.println("テテテー!掃除機終了します。\n");
}
// メソッド:ゴミを吸う
public void clean() {
System.out.println("ブオオォォォーーーン!!");
}
}
main処理では、各サブクラスをインスタンス化して、電源のオンオフと、
各々が持つ固有のメソッドを実行させてみます。
package example;
import machine.RiceCooker;
import machine.VacuumCleaner;
// クラス:メイン処理実行用
public class Example {
// メイン処理
public static void main(String[] args) {
// 炊飯器作成
RiceCooker riceCooker = new RiceCooker();
riceCooker.name = "炊飯器";
// 炊飯器を使う
riceCooker.powerOn();
riceCooker.cook();
riceCooker.powerOff();
// 掃除機作成
VacuumCleaner vacuumCleaner = new VacuumCleaner();
vacuumCleaner.name = "掃除機";
// 掃除機を使う
vacuumCleaner.powerOn();
vacuumCleaner.clean();
vacuumCleaner.powerOff();
}
}
実行結果は以下です。
ピピッ!炊飯器起動します。
もっちりふっくらご飯が炊きあがりました。
ピピー!炊飯器終了します。
テテテッ!掃除機起動します。
ブオオォォォーーーン!!
テテテー!掃除機終了します。
抽象クラスを定義したことで、サブクラスの種類が異なっても、
中身のプログラムには統一感が出ていることに注目してください。
mainから電源オンオフの処理を実行する際も、同じ方法で実装できるのでシンプルです。
abstractの使い方 まとめ
Javaのabstractについて以下説明しました。
実装をルール化できれば、個々の実装のクセを抑制でき、
誰でも同じようなプログラムを組むことができるようになります。
シンプルで品質の高いプログラムになる反面、ルールを強制しすぎると、
複雑な処理を実装する際の妨げになるというデメリットも時にはあります。
特徴をよく理解して、使いどころを上手に判断できるようになりましょう!