クラスの拡張をするに当たっては、メンバー(フィールドとメソッド)の実装継承と、実装の再定義(オーバーライド)について理解をする必要があります。
次の例では、クラスBaseを拡張して、クラスAを作成しています。クラスBaseのことを、クラスAの「スーパークラス」「親クラス」と呼びます。また、クラスAのことを、クラスBaseの「サブクラス」「子クラス」と呼びます。このように、クラス名の後に、extendsを書いて、そのクラスのスーパークラスとなるクラス名を記述することにより、クラスの拡張ができます。
class A extends Base { // 拡張のためのコード }
クラスを拡張することにより、クラスAはクラスBaseが公開しているフィールドやメソッドを実装していることになります。このことを、「クラスAはクラスBaseのフィールドとメソッドを継承(inherit)している」と表現します。このため、「Aには追加したいフィールドやメソッドについてのみコーディングをすればいい」ということになります。
また、サブクラスはスーパークラスがサブクラスへアクセスを許しているフィールドやメソッドを利用できます。例えば、BaseクラスがbaseFieldフィールドを持ち、baseMethodメソッドを実装して、両方ともサブクラスが使えるように宣言していたとすると、AクラスはbaseFieldフィールドへアクセスできますし、baseMethodメソッドを呼び出すこともできます。この辺りの話は次回以降の記事で取り上げます。ここでは、そういうものがあるという程度の理解で結構です。
アクセス制限については、これまで説明をしていませんが、次回以降の記事で予定しています。詳細はそのときに説明しますので、ここでは簡単な説明だけをしておきます。
これまで「public」というキーワードがついているメソッドやフィールドがあったと思いますが、これは、どのクラスにも公開されていることを意味します。「private」というキーワードがついているメソッドやフィールドは、そのクラスでしか使えません。「protected」というキーワードがついているメソッドやフィールドは、そのクラスのサブクラスがアクセスできます。public、private、protectedが付いていないメソッドやフィールドについては、同一パッケージ内のクラスからアクセスができます。この4点が基本です。
例として、sample12.Baseクラスを用意し、これをスーパークラスとして利用するsample12.Aクラスを用意してみましょう。文法を理解するためのものなので、実用性はまったくありませんが、ここまでの説明を理解する役には立ちます。単純にbaseFieldフィールドを宣言して1で初期化し、baseMethodメソッドは常にfalseを返すよう実装しています。
package sample12; class Base { int baseField = 1; boolean baseMethod() { return false; } }
sample12.Aクラスはsample12.Baseクラスを拡張して、aFieldフィールドとaMethodメソッドを追加しています。このメソッドでは、スーパークラスであるsample12.BaseクラスからbaseFieldフィールドとbaseMethodメソッドを継承しているため、ほかのクラスから使えるようになっています。sample12.Aクラスの中にあるメソッドでもbaseFieldフィールドとbaseMethodメソッドは利用できるので、aMethodメソッド内で使っています。
package sample12; class A extends Base { int aField = 10; boolean aMethod() { //スーパークラスのフィールドとメソッドを利用 System.out.println("aMethod:baseField:" + baseField); return !baseMethod(); } }
処理内容は単純ですが、念のため確認をしておきます。「"aMethod:baseField:" + baseField」を出力して、「!baseMethod()」の計算結果を返すという処理です。baseMethod()は、sample12.Baseクラスから継承したメソッドなので、必ず「false」となりますから、「!baseMethod()」は常に「true」となります。
本連載ではこれまで出てきませんでしたが、「!」は「論理否定」の単項演算子で、後に続く値を否定します。つまり、「!false」は「true(falseではない)」となり、「!true」は「false(trueではない)」となります。
最後に、これらを利用するsample12.Sample02クラスを作成します。起動メソッド(mainメソッド)を持ち、その中でsample12.Baseクラス、sample12.Aクラスのインスタンスを生成し、それぞれが持つフィールドにアクセスをしたり、メソッドを呼び出したりしています。
package sample12; public class Sample02 { public static void main(String[] args) { Base base = new Base(); A a = new A(); // Baseクラスのフィールドとメソッド System.out.println("base.baseField :" + base.baseField); System.out.println("base.baseMethod():" + base.baseMethod()); // AクラスはBaseクラスと同じフィールドとメソッドを持つ System.out.println("a.baseField :" + a.baseField); System.out.println("a.baseMethod() :" + a.baseMethod()); // Aクラス独自フィールドへのアクセス System.out.println("a.aField :" + a.aField); // Aクラス独自メソッドの呼び出し System.out.println("a.aMethod() :" + a.aMethod()); // baseとaは別インスタンスなので、 // base.baseFieldとa.baseFieldの値は別になる base.baseField = 3; System.out.println("base.baseField :" + base.baseField); System.out.println("a.baseField :" + a.baseField); a.baseField = 30; System.out.println("base.baseField :" + base.baseField); System.out.println("a.baseField :" + a.baseField); } }
実行結果は、次のようになります。
base.baseField :1 base.baseMethod():false a.baseField :1 a.baseMethod() :false a.aField :10 aMethod:baseField:1 a.aMethod() :true base.baseField :3 a.baseField :1 base.baseField :3 a.baseField :30
sample12.AクラスにはbaseFieldフィールドやbaseMethodメソッドの宣言はありませんが、きちんと使えていて、Baseクラスのものと同じ結果になっていることが分かります。sample12.Aクラス独自のフィールドへはアクセスできて「a.aField :10」という結果が出ています。aMethodメソッドの呼び出しにより、「aMethod:baseField:1」が出力されてから、「a.aMethod() :true」という正しい結果も出力されています。
baseとaは別インスタンスなので、base.baseFieldとa.baseFieldの値は別になっています。base.baseFieldが3になってもa.baseFieldの値は1のままですし、a.baseFieldの値を30にしても、base.baseFieldには影響がありません。
どうでしょうか、クラスの拡張をすることにより、サブクラスはスーパークラスのフィールドやメソッドをあらためてコーディングしなくてもいいことが理解できたでしょうか。さて、クラスを拡張するに当たっては、次の項目について検討が必要です。1.と2.については、実装の仕方はいま確認をしたところなので、いいでしょう。ここからは、残りの項目について順番に見ていくことにします。
すべてのクラスは、Objectクラスを拡張していることを最初に書きました。これはJava APIドキュメントを見ると分かりますが、どのクラスもスーパークラスをたどっていくと、Objectクラスにたどり着くのです。実は、Javaでは、クラス宣言時にextendsを使ってスーパークラスを指定しない場合は、暗黙のうちにObjectクラスを拡張します。ですから、equalsメソッドやhashCodeメソッドなどはメソッドを実装しなくても使えるようになっています。試しに、sample12.Baseクラスでこれらのメソッドが使えるかサンプルコードを書いて実行してみるといいでしょう。次のように書くことができ、コンパイルエラーは出ません。実行もできます。
package sample12; public class SampleBase { public static void main(String[] args) { Base base = new Base(); System.out.println("base.equals(null):" + base.equals(null)); System.out.println("base.hashCode() :" + base.hashCode()); System.out.println("base.toString() :" + base.toString()); } }
base.equals(null):false base.hashCode() :17510567 base.toString() :sample12.Base@10b30a7
もし、クラスの振る舞いを変えたいのであれば、スーパークラスのメソッドを「オーバーライド(override、無視する)」するために、同名のメソッドを宣言し、あらためて実装します。単に拡張しただけでも、メソッドは“継承”のみされるのですが、このように明示的に実装をすることによって、プログラムの動作を変更できるのです。
オーバーライドをする場合は、アノテーションの「@Override」を書いて、次の行にオーバーライドしたいメソッドの宣言と新しい処理を記述します。実は、アノテーションについては書かなくてもコンパイラではエラーが出ません。しかし、単純ミスを防ぐためにはこれを記述する癖を付けておいた方がいいですし、後ほど説明するEclipseの機能でメソッドのオーバーライドをすると、これが自動で付きますから覚えておくようにしてください。アノテーションについては、後の連載で別途説明しますので、ここでは単純に「@Override」と書いておけばいいと覚えれば十分です。
@Override 戻り型 メソッド名(仮パラメータ型 仮パラメータ名) { 新しい処理 }
それでは、sample12.Aクラスを拡張するsample12.Bクラスを用意し、aMethodメソッドのオーバーライドをしてみましょう。全部自分で記述してもいいですが、Eclipseにはメソッドのオーバーライドを簡単にするための機能が付いているので、それを使ってみましょう。
次ページでは、さらにオーバーライドについて説明し、キーワード「super」でスーパークラスへアクセスする方法にも触れます。
Copyright © ITmedia, Inc. All Rights Reserved.