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とそれを実装するクラスでも成り立つのですね。
もしプログラムもどきを実行してみたければ、日本語のところを適当なアルファベットの単語で置き換え、実装部分を例えば
System.out.println(“直火方式、熾火むらしでおいしく炊く”);
のようにしてみてください。またinterfaceを格納するファイルは、「interface名.java」としてください。
オーブン機能付き電子レンジ
電子レンジといえば温めるのが専門。冷え切ったおかずを温めたり、お燗をつけたり、大活躍です。オーブンは焼くのが専門です。パンを焼いたり、ケーキを焼いたり、グラタンを作ったりと便利です。最近はその二つの機能を併せ持った、オーブン機能付き電子レンジが普通です。
電子レンジとオーブンのinterfaceが定義されているとき、この二つのどちらの機能も実装した、オーブン機能付き電子レンジ「なんでもくん」というクラスを作ってみましょう。
このように二つのinterfaceをひとつのクラスで実装することが可能です。implementsの後ろに二つ以上のinterface名が指定できます。実はJavaでは継承に関して、ひとつのクラスが二つのスーパークラスを持つこと(多重継承といいます)は許されていません。つまりextendsの後ろにはひとつしかクラス名を指定できません。interfaceとちがうところなので気をつけましょう。
この他にもinterfaceと抽象クラスの違いはいろいろありますので、次にまとめておきます。
・ 変数
抽象クラスでは、サブクラスに共通して必要なインスタンス変数を指定できました。保育園児クラスの氏名がそうでした。interfaceでは次のような頭に「public static final」が付いた変数しか指定できません。これは最初に与えたデータを変更することができない定数として扱われます。次のnは5のままで、変更できません。(staticについてはまた別の章で詳しく解説します。)
public static final int n = 5;
「public static final」はinterface内では自動的に付加されるので指定する必要はありません。
int n = 5;
・ メソッド
抽象メソッドはどちらもOKです。
interfaceについては「public」と「abstract」を省略できます。もしアクセス指定子をつけるならば、「public」でなければなりません。
中身の定義されたメソッドは、抽象クラスではOKです。保育園児クラスのラジオ体操をする、がそうでした。サブクラスが共通して持つメソッドをここでくくりだすことができます。
interfaceでは抽象メソッド以外のものは一切指定できません。
抽象クラスとinterfaceの文法的な違いはお分かりいただけたでしょうか。Javaでの多重継承はinterfaceを利用しないとできません。また抽象メソッドと「static final」な変数のみの抽象クラスは、interfaceに書き換えることができます。