出来上がったクラスを、次のように編集します。ここではスペースの都合上から、コメントや無駄な行を削除しています。sample12.Aクラスでは、「"aMethod:baseField:" + baseField」を出力して、「!baseMethod()」の計算結果(true)を返すという処理でした。sample12.Bクラスでは、代わりに「"オーバーライド済 :"」と出力してから、必ず「false」を返すというメソッドに変更しています。
class B extends A { @Override boolean aMethod() { System.out.println("オーバーライド済 :"); return false; } }
次に、これを利用するsample12.Sample03クラスを作成します。起動メソッド(mainメソッド)を持ち、その中でsample12.Bクラスのインスタンスを生成し、それが持つフィールドにアクセスしたり、メソッドを呼び出したりしています。比較しやすくするために、同様の処理をsample12.Aクラスについても行っています。
package sample12; public class Sample03 { public static void main(String[] args) { B b = new B(); // BクラスはAクラスからフィールドとメソッドを継承 System.out.println("b.baseField :" + b.baseField); System.out.println("b.baseMethod() :" + b.baseMethod()); // BクラスはAクラスのaMethodメソッドをオーバーライド System.out.println("b.aMethod() :" + b.aMethod()); System.out.println("-------------------------"); // Aクラスの場合 A a = new A(); System.out.println("a.baseField :" + a.baseField); System.out.println("a.baseMethod() :" + a.baseMethod()); System.out.println("a.aMethod() :" + a.aMethod()); } }
実行結果は、次のようになります。結果から、sample12.Bクラスは、sample12.Aクラスのフィールドやメソッドを継承していることが分かります。また、aMethodメソッドの処理結果を見ることにより、sample12.Bクラスとsample12.Aクラスとは振る舞いが変わっていて、確かにsample12.Bクラスがsample12.AクラスのaMethodメソッドをオーバーライドしていることが分かります。
b.baseField :1 b.baseMethod() :false オーバーライド済 : b.aMethod() :false ------------------------- a.baseField :1 a.baseMethod() :false aMethod:baseField:1 a.aMethod() :true
Javaでは、「単一継承モデル」といわれるモデルを採用しているため、クラスの拡張をする場合に、スーパークラスとして指定できるのは1つのクラスだけです。つまり、1つのクラスを拡張して新しいクラスを作れますが、2つ以上のクラスを組み合わせて拡張して新しいクラスを作れません。
2つ以上のクラスを組み合わせて拡張することは、「実装の多重継承」といわれますが、Javaではこれを許していません。しかし、多重継承の恩恵を得るために、Javaでは「インターフェイスの多重継承」はできます。実装の多重継承が有用な場面もありますが、問題となる実装がされてしまうこともあります。Javaはシンプルな実装ができるように、多重継承については限定する方向で言語設計されたのです。
ちなみにインターフェイスの拡張をするためにも、extendsキーワードを使います。今回は詳細を説明しませんが、構文自体は覚えておくといいでしょう。インターフェイスは多重継承をサポートしていますから、複数のインターフェイスを拡張できます。
interface インターフェイス名 extends スーパーインターフェイス名 { メソッドの宣言 }
interface RunnablePoint extends java.lang.Runnable { int getX(); int getY(); void setX(int x); void setY(int y); }
例では、java.lang.Runnableインターフェイスを拡張しているので、RunnablePointは宣言されているメソッド以外に、java.lang.Runnableインターフェイスで宣言されているrunメソッドについてインターフェイス継承しているということになります。ですから、RunnablePointインターフェイスを実装するクラスは、getXメソッドなどの4つのメソッドだけでなく、runメソッドも実装する必要があります。
キーワード「super」を使うことにより、スーパークラスへアクセスできます。このキーワードは、クラスのすべてのstaticではないメソッドの中で利用できます。thisキーワードを使うと、自分自身のインスタンスを参照できましたが、このキーワードは自分自身をスーパークラスのインスタンスと見なして、スーパークラスのフィールドやメソッドへアクセスできます。
package sample12; class C extends A { @Override boolean aMethod() { // スーパークラスのaMethodメソッドを呼び出す boolean b = super.aMethod(); System.out.println("super.aMethod() :" + b); System.out.println("オーバーライド済 :"); return false; } }
次のようなクラスを用意することにより、sample12.Cクラスの動作を確認できます。
package sample12; public class Sample04 { public static void main(String[] args) { C c = new C(); System.out.println("c.aMethod() :" + c.aMethod()); } }
実行結果は、次のとおりです。最初の行は、スーパークラスのaMethodメソッドにより出力されています。2行目の結果を見て分かるように、スーパークラスのaMethodメソッドの計算結果のtrueが変数bに代入されています。3行目はsample12.CクラスのaMethodメソッド内の処理で、sample12.CクラスのaMethodメソッドの計算結果は4行目よりfalseだったことが分かります。どうでしょう、思ったとおりの結果になりました。
aMethod:baseField:1 super.aMethod() :true オーバーライド済 : c.aMethod() :false
クラスを拡張した際には、そのクラスのオブジェクトの等値性をどのように定義するのか、きちんと考える必要があります。Stringのように、「表現する文字列が同じものなら、インスタンスは別でも、equalsメソッドの結果はtrue」としたいクラスを作りたくなることもあります。その場合は、Objectクラスのequalsメソッドをオーバーライドする必要が生じてきます。
Objectクラスのequalsメソッドは、保持するデータが等値(Equality)であるかではなく、インスタンスが同一(Identity)であるかどうかを判定する処理となっているため、これをクラスに合わせて等値判定をする処理にします。
equalsメソッドをオーバーライドした場合は、hashCodeメソッドのオーバーライドも必要です。このメソッドはコレクションフレームワークのクラス(java.util.HashMapなど)で等値判定に利用されていることがあります。equalsメソッドにより等値判定が変わるのであれば、hashCodeメソッドもその等値判定に合わせた処理へ変更する必要があります。
ちょっと大変そうに聞こえたかもしれませんが、実は、これらを簡単に実装するためのクラスが、「Apache Commons Lang」にあります。これに含まれるorg.apache.commons.lang.builder.EqualsBuilderやorg.apache.commons.lang.builder.HashCodeBuilderです。実際に変更が必要な場面に出会ったら、これらの利用を検討してみるといいでしょう。
以上で説明はお終いですが、クラスの拡張について理解できたでしょうか。ポリモーフィズムにより、「クラスBがクラスAを拡張している場合、クラスBは、クラスAとしての形態と、クラスB としての形態と両方を持つことができる」ということについて、java.lang.Objectとjava.lang.Stringを使って説明をしました。下記をポイントとして覚えておきましょう。
次に、クラスを拡張して、新しいクラスを作成する方法は理解できたでしょうか。「既存のクラスへ新しいフィールドや新しいメソッドを追加するには、extendsによるクラス拡張をすればいい」ということがポイントです。そして、「その際、既存クラスのフィールドやメソッドは継承される」という点もポイントとして覚えておきましょう。
メソッドのオーバーライドをすることにより、拡張したクラスの方でスーパークラスとは違った振る舞いを持たせることができることも説明をしました。こういった定義をできることが、オブジェクト指向の“肝”でもありますから、よく理解をしておいてください。また、superを使うことにより、スーパークラスのフィールドへアクセスしたり、メソッドを呼び出しできることも説明しました。これらを理解することにより、思った通りの処理を無駄なコーディングを追加することなく実現できるようになります。
今回の内容を理解することにより、クラスの拡張がどういったものか理解できたと思います。ただし、自分のクラスが拡張されて使われることまで考えてクラスの設計をしたり実装をしたりするには、このほかにも知っておくべき基礎知識があります。次回は、これまで詳しく説明をしてこなかったコンストラクタ、アクセス制御について説明する予定です。これらを理解することにより、拡張性や使い勝手も考慮に入れたクラス設計や実装がきちんとできるようになります。お楽しみに。
今回作ったサンプルのソースコードはこちらからダウンロードできます。
小山博史(こやま ひろし)
情報家電、コンピュータと教育の研究に従事する傍ら、オープンソースソフトウェア、Java技術の普及のための活動を行っている。長野県の地域コミュニティである、SSS(G)やbugs(J)の活動へも参加している。
著書に「基礎Java」(インプレス)、共著に「Javaコレクションフレームワーク」(ソフトバンククリエイティブ)、そのほかに雑誌執筆多数。
Copyright © ITmedia, Inc. All Rights Reserved.