<<前ページ|目次|次ページ>> | ||||||||||||||||||||||||||||||||||||||||
〜抽象クラスとinterface〜 | ||||||||||||||||||||||||||||||||||||||||
13章でチラッと出てきた抽象クラスをもう一度見直してみましょう。さらにinterfaceまで話を進めます。 第13章「保育園児の豚汁作り」の復習です。保育園児のクラスを抽象クラスとして定義しました。覚えていますか?青さんや黄さんといったクラスの、共通部分のくくりだし(汎化)でしたね。ここで「豚汁の準備をする」メソッドを抽象メソッドとしました。青さん、黄さん、桃さんで、「豚汁を準備する」行為がそれぞれ異なるためです。具体的な内容は各サブクラスで定義しました。野菜を切る、とか野菜の皮をむく、などです。青さんのクラスからオブジェクトは生成できるけれども、抽象クラスである保育園児のクラスからは生成できませんでしたね。思い出しましたか。 ここでは抽象クラスをもう一度見ておきましょう。 抽象クラスには抽象メソッドが含まれており、サブクラスで必ずオーバーライドしなければなりません。簡単な例で見ていきましょう。 [A.java] |
||||||||||||||||||||||||||||||||||||||||
public abstract class A { public abstract void m1(); // 抽象メソッド public abstract void m2(); // 抽象メソッド public void m3() { System.out.println("Aのm3"); } } |
||||||||||||||||||||||||||||||||||||||||
クラスAはこう言っています。「m1とm2の中身は、私のサブクラスでそれぞれ決めておくれ。お好きなようにね。ただし必ずm1とm2についてはオーバーライドしてきちんと定義するんだよ。m3は一応こっちで決めておくから、オーバーライドしたければお好きに。私はオブジェクトを作れないから、サブクラスたち、頼むよ。」 クラスAは二つの抽象メソッドm1とm2があります。メソッドの定義部分{ }は指定せず、すぐにセミコロン;です。抽象メソッドを含むクラスは、必ず「abstract」をつけて抽象クラスとして定義しなければなりません。そうでないとコンパイラエラーになります。ではこれを継承するクラスBです。 [B.java] |
||||||||||||||||||||||||||||||||||||||||
public class B extends A { public void m1() { System.out.println("Bのm1"); } public void m2() { System.out.println("Bのm2"); } } |
||||||||||||||||||||||||||||||||||||||||
クラスAで抽象メソッドとして宣言されていたメソッドm1とm2をオーバーライドしています。もしどちらか、あるいは両方がオーバーライドされなかった場合、クラスBも「abstract」をつけて抽象クラスとしなければコンパイルエラーになります。 クラスBのオブジェクトは、スーパークラスで定義されたm3とここで定義されたm1、m2が呼び出し可能です。ではやってみましょう。 [Main.java] |
||||||||||||||||||||||||||||||||||||||||
public class Main { public static void main(String[] args) { A x = new B(); // A x = new A(); ではエラー x.m1(); x.m2(); x.m3(); } } |
||||||||||||||||||||||||||||||||||||||||
出力結果は次のようになります。 Bのm1 Bのm2 Aのm3 ここで3行目を次のようにすると抽象クラスからオブジェクトを生成しようとしているため、コンパイルエラーになります。 A x = new A( ); 上ではクラスBでいっぺんにオーバーライドを済ませていましたが、次のように何段階かに分けてもかまいません。クラスAの抽象メソッドm1とm2は、クラスBとCでそれぞれオーバーライドしています。ここで注意してほしいのは、クラスBを抽象クラスにしてあることです。この段階ではスーパークラスAのメソッドm2がまだオーバーライドされていないからです。 [A.java] |
||||||||||||||||||||||||||||||||||||||||
public abstract class A { public abstract void m1(); public abstract void m2(); public void m3() { System.out.println("Aのm3"); } } |
||||||||||||||||||||||||||||||||||||||||
[B.java] | ||||||||||||||||||||||||||||||||||||||||
abstract class B extends A { public void m1() { System.out.println("Bのm1"); } } |
||||||||||||||||||||||||||||||||||||||||
[C.java] | ||||||||||||||||||||||||||||||||||||||||
public class C extends B { public void m2() { System.out.println("Cのm2"); } } |
||||||||||||||||||||||||||||||||||||||||
[Main.java] | ||||||||||||||||||||||||||||||||||||||||
public class Main { public static void main(String[] args) { A x = new C(); x.m1(); x.m2(); x.m3(); } } |
||||||||||||||||||||||||||||||||||||||||
出力結果は次のようになります。 Bのm1 Cのm2 Aのm3 クラス図では抽象クラスのクラス名と抽象メソッドのメソッド名は斜体で指定します。上のクラスA、B、Cの関係をクラス図にしてみます。 抽象クラスに似たものにinterfaceがあります。抽象メソッドのみの抽象クラスは、interfaceに書き換えることができます。次は抽象クラスとinterfaceで同じことをしています。比較してみましょう。 [抽象クラスの場合] |
||||||||||||||||||||||||||||||||||||||||
public abstract class A { public abstract void m1(); public abstract void m2(); } public class B extends A { public void m1() { System.out.println("Bのm1"); } public void m2() { System.out.println("Bのm2"); } } |
||||||||||||||||||||||||||||||||||||||||
[interfaceの場合] | ||||||||||||||||||||||||||||||||||||||||
public interface A { void m1(); void m2(); } public class B implements A { public void m1() { System.out.println("Bのm1"); } public void m2() { System.out.println("Bのm2"); } } |
||||||||||||||||||||||||||||||||||||||||
抽象クラスの方はいいですね。interfaceのAを見てみましょう。 Aのアクセス指定子はpublicですが、何もなければパッケージ内からのみアクセス可能になります。クラスのアクセス指定子と同じですね。interfaceの場合、ここにprivateやprotectedは指定できません。 メソッドには「public」と「abstract」が自動的に付加されます。せっかく勝手に付けてくれるのですっきりさせておきましょう。 ではクラスBを見ましょう。ここでは「extends」ではなく「implements」(スペルに注意)の後にAの指定があります。前者が「継承する」というのに対し後者は「実装する」といいます。あとは上の抽象クラスを継承した場合と同じです。クラス図も似ていますが、interfaceの場合は点線で結びます。 ひとつ注意事項があります。interfaceの場合、A内のm1やm2にアクセス指定子がないので、実装するときうっかりしてm1やm2のアクセス指定子をpublic(太字の部分)と指定しないとコンパイルエラーになります。なぜならA内のm1、m2はpublicだからです。アクセス指定子に関しては、継承と同じ規則が適用されるので気をつけましょう。(→第16章 狭くしないで) interfaceには、抽象メソッドと、「static final」な変数(後で説明します)しか含むことができません。コンストラクタも定義できません。抽象クラスのように、インスタンス変数や中身が定義されたメソッドは指定できないのです。ではなぜinterfaceなどあるのか...次は“実装”という面からinterfaceを見ていきましょう。 先ごろ我が家の炊飯ジャーが壊れました。新しいのを買って使ったところ、なんと説明書を見ないでご飯を炊くことができたのです!当たり前ですか?メーカーもちがうし、前のはマイコン炊飯ジャー、今度のは真空圧力IH炊飯ジャーなのに... 実はどちらの炊飯ジャーにも、炊飯というボタンがあって、それをポンと押すだけでよかったのです。炊飯ジャーは炊飯と保温ができる、ということを幸いにも知っていたために、「これは炊飯ジャーである。では多分炊飯のボタンを押せばご飯が炊けるんだろうなぁ。」と考えたわけです。実際二つのジャーでは、炊飯の仕方はちがうのでしょうが、そんなこたぁこっちは気にしちゃいません。なにしろおいしく炊ければいいのです。 ではこれをまたまたプログラムもどきで書き直してみましょう。interfaceを使います。 各クラスを囲んだ色と、上の図の色を対応させて見てください。interfaceでは、炊飯ジャーはどんなことができるか、つまり炊飯ジャーを使う人に対して炊飯と保温の二つのことはできると保証しますよ、ということを示しています。おかげで黄色く囲まれた私は、どちらの炊飯ジャーを使っても、同じ x.炊飯( ); x.保温( ); で、炊飯と保温ができるのです。これが図の炊飯や保温のボタンを押すことに対応します。 実際二つのジャーがどのようにして炊飯や保温をしているかは、水色の線で囲んだ部分で定義します。炊飯ジャーの実装をする、といいます。これは黄色の私からは全く見えなくていいのです。また見えてはいけません。おかずだって作らなくてはならないのに、ご飯の炊き方まで一々監督していられませんから。 つまり黄色のクラスでは、ピンクのinterfaceを見れば、水色の実装部分を見なくても炊飯ジャーが使える、というわけです。黄色と水色の仲立ちをしているのが、ピンクのinterfaceになります。まさにインタフェースですね。 「x.炊飯();」で、xの参照するオブジェクト(マイコン炊飯ジャーだったり真空圧力IH炊飯ジャーだったり)の炊飯が行われます。スーパークラスとサブクラスのポリモーフィズムで解説したことが、interfaceとそれを実装するクラスでも成り立つのですね。 |
||||||||||||||||||||||||||||||||||||||||
<<前ページ|目次|次ページ>> | ||||||||||||||||||||||||||||||||||||||||
Copyright (c) 2004-2005 Nagi Imai All Rights Reserved. | ||||||||||||||||||||||||||||||||||||||||