
継承やオーバーライドで簡単にクラスを“拡張”しよう
株式会社ガリレオ
小山博史
2009/11/13
Java標準の拡張されたクラスを使ってみよう
それでは、Javaの標準APIの中にあるクラスを使って、クラスの拡張について理解をしてみましょう。あるクラスを拡張して作成されたクラスという例はたくさんありますが、ここではjava.lang.Objectクラスとjava.lang.Stringクラスを使ってみましょう。
注意 |
| 本稿では、APIドキュメントを参照します。ここではオンラインのドキュメントを参照していますが、オフライン環境でも確認はできます。APIドキュメントは第1回で「C:\application\jdk6」へインストールしていますから、オフライン環境で確認する場合は、そちらを利用してください。 |
■ APIドキュメントでは
JDK 6のAPIドキュメントのStringクラスのページを参照すると、java.lang.Stringクラスは、java.lang.Objectクラスを拡張していることが分かります(図4)。JDK 6のAPIドキュメントでは、拡張している関係をこのように表現しているのです。
![]() |
| 図4 JDK 6 APIドキュメントでの拡張表現 |
APIドキュメントを見ると分かりますが、「クラス java.lang.Object から継承されたメソッド」として、cloneやfinalize、getClassといったメソッドが挙げられています。JDK 6のAPIドキュメントのObjectクラスのページも見てみましょう。サンプルプログラムでは、java.lang.Objectクラスにある次の4つのメソッドを使ってみます。
- boolean equals(Object obj)
thisとobjが等しいかを調べるメソッド - int hashCode()
インスタンスのハッシュコード値を計算するメソッド - String toString()
thisの文字表現を計算するメソッド - Class<?> getClass()
このObjectの実行時クラスを返すメソッド
各メソッドの簡単な説明を添えましたが、サンプルの動作を理解するのに必要な最低限のことしか書いてありません。正確なAPIの処理内容については、APIドキュメントを参考するなどしてください。
ここで、再度、JDK 6のAPIドキュメントのStringクラスのページを確認して、Stringクラスもequalsメソッド、hashCodeメソッド、toStringメソッドがある点や、getClassメソッドは「クラスjava.lang.Objectから継承されたメソッド」に含まれるという点に注目してください。
■ 拡張されたクラスを使うサンプルを作る
それでは、「sample12.Sample01」という名前のクラスで、これらの動作を確認するサンプルプログラムを作成してみます。フィールドとして、java.lang.Object(以降、「Object」)クラスの配列、java.lang.String(以降、「String」)クラスの配列を持つようにします。コンストラクタでは各フィールドの初期化をして、インデックス0の要素とインデックス2の要素が同じインスタンスを参照するようにしておきます。
また、runメソッドを用意して、そこに主となる処理を記述することにします。プログラム起動用のmainメソッドでは、sample12.Sample01クラスのインスタンスを生成して、そのrunメソッドを呼ぶという単純な処理にしておきます。
sample12/Sample01.javapackage sample12;
public class Sample01 {
Object[] o = new Object[3];
String[] s = new String[3];
Sample01() {
// o[1] は o[0] とは違うインスタンスを参照
// o[0] と o[2] は同じインスタンスを参照
o[0] = new Object();
o[1] = new Object();
o[2] = o[0];
// s[1] は s[0] とは違うインスタンスを参照
// s[0] と s[2] は同じインスタンスを参照
s[0] = new String("s0");
s[1] = new String("s0");
s[2] = s[0];
}
// 略
public static void main(String[] args) {
new Sample01().run();
}
}
情報を表示するために、便利なメソッドをいくつか用意します。ObjectクラスのhashCodeメソッド、toStringメソッドの計算結果を情報を表示するprintObjectInfoメソッドを用意します。各メソッドの結果を出力しているだけです。
void printObjectInfo(String title, Object o) {
System.out.println("printObjectInfo:" + title);
System.out.println("\t" + o.hashCode());
System.out.println("\t" + o.toString());
}
StringクラスのhashCodeメソッド、toStringメソッドの計算結果を情報を表示するprinStringInfoメソッドも用意します。こちらは、Stringクラスの各メソッドの結果を出力しているだけです。こちらも難しいことは何もありません。
void printStringInfo(String title, String s) {
System.out.println("printStringInfo:" + title);
System.out.println("\t" + s.hashCode());
System.out.println("\t" + s.toString());
}
Objectクラス、Stringクラスのequalsメソッドの結果を表示するprintEqualメソッドも用意します。あらかじめ用意しておいた配列の各要素についてequalsメソッドで比較をし、その結果を表示します。
void printEqual() {
System.out.println("o[0]:o[1]:"+o[0].equals(o[1]));
System.out.println("o[0]:o[2]:"+o[0].equals(o[2]));
System.out.println("o[1]:o[2]:"+o[1].equals(o[2]));
System.out.println("s[0]:s[1]:"+s[0].equals(s[1]));
System.out.println("s[0]:s[2]:"+s[0].equals(s[2]));
System.out.println("s[1]:s[2]:"+s[1].equals(s[2]));
}
Objectクラス、StringクラスのgetClassメソッドを実行するexecGetClassメソッドも用意します。ここで注目してもらいたいのは、Stringクラスについても、「s[0].getClass();」のように、getClassメソッドを呼び出せる点です。StringクラスではgetClassメソッドは「Objectクラスから継承されたメソッド」とAPIドキュメントには記載されていました。このメソッドはStringクラスのソースコードには処理が記述されていません。Objectクラスのソースコードに記述されている処理を利用しているのです。
void execGetClass() {
System.out.println("o[0].getClass():" + o[0].getClass());
System.out.println("o[1].getClass():" + o[1].getClass());
System.out.println("o[2].getClass():" + o[2].getClass());
System.out.println("s[0].getClass():" + s[0].getClass());
System.out.println("s[1].getClass():" + s[1].getClass());
System.out.println("s[2].getClass():" + s[2].getClass());
}
最後に、runメソッドです。Objectクラスの配列について、各要素を使ってprintObjectInfoメソッドを実行します。また、Stringクラスの配列について、各要素を使ってprintStringInfoメソッドを実行します。さらに、Stringクラスの配列については、各要素を使ってprintObjectInfoメソッドも実行します。
■ インターフェイスを思い出せ
ここで、「printObjectInfoメソッドは、Objectクラスを引数として受け取るメソッドなのに、なぜだろう」と思いませんか? ObjectクラスとStringクラスは型が違うにもかかわらず、printObjectInfoメソッドの引数として使えるというのは、不思議な感じがしますが、よく考えてみましょう。
インターフェイスのことを思い出してみると、理解できるはずです。同じようなことがインターフェイスを使ったときもできました。そうです、Stringクラスは、Objectクラスを拡張したものなので、このようにObjectクラスと同じものとして扱えます。この点は重要ですから、よく覚えておいてください。
■ サンプルプログラムを実行する前に結果を想像してみよう
最後に、printEqualメソッドとexecGetClassメソッドを呼び出して終了です。
void run() {
for (int i = 0; i < o.length; i++) {
printObjectInfo("o[" + i + "]", o[i]);
}
for (int i = 0; i < s.length; i++) {
printStringInfo("s[" + i + "]", s[i]);
}
for (int i = 0; i < s.length; i++) {
printObjectInfo("s[" + i + "]", s[i]);
}
printEqual();
execGetClass();
}
それでは、プログラムを実行してみましょう。実行する前に、各メソッドの結果がどのようになるか想像してみてください。これまでの説明からどうなるかある程度分かるはずです。想像できたら、実行してください。各メソッドの実行結果が順に出力されるはずです。どうでしょう、あなたの予想は当たりましたか? 量が多いので、sample12.Sample01の各メソッドの計算結果を単位にして説明をします。
最初に、Objectクラスの配列の各要素を使ってprintObjectInfoメソッドを実行した結果です。ハッシュ値は、ご覧のとおり数値です。インスタンスが等しいかどうかを比較する際に利用されるので、Objectクラスとは違うインスタンスでは値が異なります。o[0]とo[1]とo[2]の値を確認しておきましょう。toStringメソッドの結果を見て分かるように、Objectクラスのインスタンスの文字列表現は、「java.lang.Object@ca0b6」のように「クラス名@数値」という表現になっています。
printObjectInfoメソッドの実行結果printObjectInfo:o[0]
827574
java.lang.Object@ca0b6
printObjectInfo:o[1]
17510567
java.lang.Object@10b30a7
printObjectInfo:o[2]
827574
java.lang.Object@ca0b6
次に、Stringクラスの配列の各要素を使ってprintStringInfoメソッドを実行した結果です。ハッシュ値に注目をしましょう。Objectクラスとは違って、どれも同じ値です。s[0]とs[1]は、new演算子を使って別々のインスタンスを生成したはずですが、同じ値になっています。toStringメソッドの結果も、Objectクラスとは違います。Stringクラスのインスタンスでは、自身が表している文字列そのものを返してきています。
printStringInfoメソッドの実行結果printStringInfo:s[0]
3613
s0
printStringInfo:s[1]
3613
s0
printStringInfo:s[2]
3613
s0
■ オブジェクト指向プログラミング言語で重要な「ポリモーフィズム(多態性)」とは
次に、Stringクラスの配列の各要素を使ってprintObjectInfoメソッドを実行した結果です。この実行結果について、皆さんの予想はどうだったでしょうか。ご覧のとおり、タイトルを表示している部分だけが変わっているだけで、各メソッドの実行結果は、printStringInfoメソッド内のものと同じです。
よく考えると分かりますが、printObjectInfoメソッドへはStringクラスのインスタンスがわたってきているわけですから、StringクラスのhashCodeメソッドやtoStringメソッドが実行されます。printObjectInfoメソッドでは、Objectクラスのインスタンスを使っているかのように処理を記述していますが、実際はここにはObjectクラスを拡張したクラスのインスタンスが来て、それぞれに記述されたhashCodeメソッドやtoStringメソッドが実行されることになるのです。
慣れないと不思議な感じがしますが、このような機能があるために、拡張したクラスを増やしても、従来の処理に修正しなくても済むことが多いので、便利なのです。
printObjectInfoメソッドの実行結果printObjectInfo:s[0]
3613
s0
printObjectInfo:s[1]
3613
s0
printObjectInfo:s[2]
3613
s0
sの各要素については、printStringInfoメソッドへはString型のインスタンスとして渡され、printObjectInfoメソッドへはObject型のインスタンスとして渡されました。sの各要素はこのようにどちらの型のインスタンスとしても使えるのです。
この機能を「ポリモーフィズム(polymorphism、多態性)」といいます。あるクラスのインスタンス(この例ではsの各要素)が、そのクラス自身(この例ではjava.lang.String)としての形態を持つと同時に、あるいはスーパークラス(この例では、java.lang.Object)としての形態を持つことから、このようにいいます。オブジェクト指向プログラミング言語で重要な用語ですから、覚えておきましょう。
■ 型の互換性がある
次に、printEqualメソッドを実行した結果です。こちらは、予想しやすかったのではないでしょうか。以前も紹介しましたが、Stringクラスでは表現している文字列が同じであれば、equalsメソッドの結果はtrueとなります。ここでは、Objectクラスの「o[0]とo[1]」、「o[1]とo[2]」が、equalsメソッドで同値かどうか計算をすると、falseという結果となることについて、理解できれば大丈夫です。
printEqualメソッドの実行結果o[0]:o[1]:false
o[0]:o[2]:true
o[1]:o[2]:false
s[0]:s[1]:true
s[0]:s[2]:true
s[1]:s[2]:true
最後がgetClassメソッドを実行した結果です。ここでは、StringクラスでもgetClassメソッドが使えることが確認できれば十分です。結果は見てのとおり、各インスタンスのクラス情報が取得できていて、同じクラスのインスタンスについては同じ値が出力されています。
getClassメソッドの実行結果o[0].getClass():class java.lang.Object
o[1].getClass():class java.lang.Object
o[2].getClass():class java.lang.Object
s[0].getClass():class java.lang.String
s[1].getClass():class java.lang.String
s[2].getClass():class java.lang.String
どうでしょう、拡張の元となったクラスと、拡張されたクラスの関係や、機能の差について、何となく理解できたでしょうか。StringクラスはObjectクラスとしても振る舞えるという「型の互換性がある」点は忘れないようにしてください。これが重要ポイントです。
さて、使い方が分かると、「どういった場合にクラスを拡張するといいのか」が分かるようになります。ちょっと難しい内容だったかもしれませんが、よく確認して理解しておくようにしましょう。次ページからは、文法事項を押さえて、自作のクラスを拡張する方法を解説します。
| Index | ||||||||||
|
||||||||||
【改訂版】Eclipseではじめるプログラミング バックナンバー 連載インデックスへ»
- 第1回 Eclipse 3.4で超簡単Javaプログラミング基礎入門
- 第2回 Javaで一から理解するプログラムの変数と演算子
- 第3回 プログラミングの醍醐味! Javaで“条件式”を理解する
- 第4回 プログラミングの真骨頂! Javaで“反復処理”を覚える
- 第5回 データ集合を扱うのに便利なJavaの配列と拡張for文
- 第6回 複雑なデータを表現できるクラスやフィールドって?
- 第7回 クラスの振る舞いを表すJavaの“メソッド”とは?
- 第8回 Javaの参照型を文字列操作で理解して文法を総復習
- 第9回 プログラムを「変更」しやすくする“インターフェイス”
- 第10回 Javaの実案件に必須のパッケージとインポートを知る
- 第11回 「static」でクラス共有の変数・メソッドを使いこなせ!
- 第12回 継承やオーバーライドで簡単にクラスを“拡張”しよう
- 第13回 “コンストラクタ”と初期化、本当に理解できてる?
- 第14回 再利用性の高いクラス作成に重要な“アクセス制御”
- 第15回 Javaは「抽象クラス」で実装を上手に再利用できる
- 第16回 “ネスト”した型で始める軽量Javaプログラミング!?
- 第17回 あなたの知らない、4つのマニアックなJava文法
- 第18回 強く型付けされているJavaの理解に必修の“型変換”
- 第19回 キュー構造をJavaで実装してジェネリック型を理解する
- 第20回 拡張for文の真の実力を知り、反復処理を使いこなせ
- 第21回 7ステップで理解するJavaでの列挙型/enum使用法
- 第22回 いまさら聞けない「Javadoc」と「アノテーション」入門
- 第23回 プログラマの宿命! 例外とエラー処理を理解する
- 第24回 Javaの例外処理で知らないと損する7つのテクニック
| Java Solution全記事一覧 |
TechTargetジャパン
- Scalaのパッケージ、アクセス修飾子、オブジェクト継承 (2012/5/22)
インポート、パッケージオブジェクト、抽象クラス/抽象メソッド、オーバーライド、final、シールドクラスなども - 基幹系システムでCloud SQLは使えるか試してみた (2012/5/17)
サンプルとしてMRPシステムを作成して動かし、「再帰呼び出し」などのパフォーマンスを測定して検証してみます - アジャイル管理ツール9選+Pivotal Tracker入門 (2012/5/14)
群雄割拠のアジャイルプロジェクト管理ツールを9つ紹介し、特に注目を集めているPivotal Trackerの基本的な使い方を解説します - サーバサイドJSやJavaでWebアプリが作れるXPages (2012/5/11)
Notes/Dominoの資産をサーバサイドJavaScriptやJavaで操作し、HTMLやJavaScript、CSSをUIにできる技術を紹介
|
|
キャリアアップ
スポンサーからのお知らせ
- - PR -
イベントカレンダー
- - PR -

