こんにちは。ECF Techブログ
担当 Michiharu.Tです。
Javaプログラミング入門記事の第17章をお送りいたします。
第17章のテーマは「Objectクラス」です。
本連載の初回および章立ての一覧については下記のリンクから確認できます。
本章では、Javaのオブジェクト指向の概念を支える重要なクラスであるObjectクラスについてご紹介します。
Objectクラスはすべてのクラスの親クラスです。ここでいうすべてとは、JDKにあらかじめ含まれるクラスおよびプログラマー自身が作成したクラスのすべてです。
Objectクラスを活用するメリットには次のようなものがあります。
- すべてのクラスに共通するメソッドを持たせることができる。
- すべてのクラスをObject型として扱うことができる。
JDKのクラスライブラリには、このメリットを活かして作成されたものが数多くあります。本章では、この2つのメリットについて具体例を用いて解説し、Objectクラスの活用のしかたを学びます。
17-1 メソッドの活用
最初に「すべてのクラスに共通するメソッドを持たせることができる。」というメリットについて説明します。Objectクラスには様々なメソッドが定義されています。Objectクラスはすべてのクラスの親クラスですので、Objectクラスで定義されているメソッドはすべてのクラスで使うことができます。
本節では、よく用いられるメソッドとして次の2つをご紹介します。
メソッド名 | 概要 |
---|---|
toString | インスタンスの文字列表現を定義する |
equals | 2つのインスタンスの同値を定義する |
17-1-1 toStringメソッドの動作
始めにtoStringメソッドです。Objectクラスでは次のように定義されています。
public String toString()
toStringメソッドはオブジェクトの文字列表現を定義することを目的としたメソッドです。クラスライブラリ内にあるクラスでも様々なところでtoStringメソッドが呼び出されています。その代表例がSystem.out.println
です。プログラム例を使って動きを確認してみましょう。
プログラム例(Item.java)
public class Item { //フィールド String name; //名前 int price; //価格 //コンストラクタの定義 Item(String name, int price){ //2つのフィールドに引数の値を代入 this.name = name; this.price = price; } }
商品を表すItemクラスを定義しています。2つのフィールドとそのフィールドを初期化するためのコンストラクタ。というシンプルな構成になっています。
次にItemクラスを利用するメインクラスを定義します。
プログラム例(Main.java)
public class Main{ public static void main(String[] args){ Item ringo = new Item("りんご",200); System.out.println(ringo); } }
Item型のインスタンスを生成し、そのインスタンスをそのままSystem.out.println
で表示するプログラムです。
では、作成した2つのクラスをコンパイル・実行してみましょう。
実行結果
Item@7a81197d
実行結果としてよくわからない文字列が表示されました。その理由を順に説明します。
実はSystem.out.println
では、引数として渡されたインスタンスのtoStringメソッドを呼び出し、その戻り値を表示しています。
ですが、ItemクラスにtoStringメソッドの定義はありません。ではなぜ、toStringメソッドの呼び出しができるかというとObjectクラスに定義されているtoStringメソッドが継承されているためです。
Objectクラスに定義されているtoStringメソッドは、次のような内容を表示することになっています。
クラス名@ハッシュコード
つまり、Objectクラスに定義されたtoStringメソッドがSystem.out.println
内で呼び出されることによって、上記のような実行結果が表示されたというわけです。
17-1-2 toStringメソッドの活用
次にtoStringメソッドを活かして、Itemインスタンスの情報をSystem.out.printlnで表示できるようにしてみましょう。プログラム例を示します。
(Item.java)
public class Item { //フィールド String name; //名前 int price; //価格 //コンストラクタの定義 Item(String name, int price){ //2つのフィールドに引数の値を代入 this.name = name; this.price = price; } //toStringメソッドをオーバーライドする public String toString(){ return "商品名:" + name + " 価格:" + price; } }
toStringメソッドをオーバーライドする記述を追加しました。これによりSystem.out.printlnメソッドは、オーバーライドした方のメソッドを呼び出します(下図)。
メインクラスを再度実行して確認してみましょう。
実行結果
商品名:りんご 価格:200
System.out.printlnに引数としてItemインスタンスを渡すだけで、情報を表示することができました。
このように、Objectクラスのメソッドはクラスライブラリに含まれる様々なクラスで利用されていますので、自作クラス内でオーバーライドするだけで、様々な恩恵を受けることができます。
17-1-3 equalsメソッドの動作
次にequalsメソッドの動作を見ていきましょう。equalsメソッドは 2つのインスタンスの同値 を定義するメソッドです。同値について、String型のequalsメソッドを例にご紹介します。
プログラム例(Main.java)
public class Main{ public static void main(String[] args){ String s1 = new String("hello"); String s2 = new String("hello"); System.out.println(s1 == s2); System.out.println(s1.equals(s2)); } }
2つの文字列s1とs2を用意し、演算子==
を用いた比較とequalsメソッドを用いた比較の両方を行っています。コンパイル・実行すると、次のような結果となります。
実行結果
false true
==
演算子を使った比較において「同じ」とみなされる基準は、「同じインスタンスかどうか」です。プログラム例は3,4行目でそれぞれ文字列「hello」をインスタンスとして生成しています。したがって、変数s1,s2は下図のように、それぞれ異なるインスタンスを指しています。そのため==
演算子で判定を行うと「s1とs2は異なる」という判定になります。
一方でStringクラスのequalsメソッドでは「同じ文字列であれば同じとみなす」という定義がなされています。s1とs2は指し示すインスタンスは異なりますが、保持している文字列は同じなのでequalsメソッドの判定ではtrue
と表示されています。
Javaではこのように「同じ」の判定基準が2つあります。2つの変数が同じインスタンスを指している場合、2つの変数は「等価」である。と言います。一方、2つの変数をequalsメソッドで比較したときにtrueである場合、2つの変数は「同値」である。と言います。
17-1-4 equalsメソッドの活用
次はItemクラスでequalsメソッドをオーバーライドして、Itemインスタンスにおける 「同値」 を定義してみましょう。今回は、2つのインスタンスにおいて名前と価格がどちらも一致していたら「同値」とすることにします。
Itemクラスでequalsメソッドをオーバーライドします。次のようなプログラムとなります。
プログラム例(Item.java)
: public boolean equals(Object obj){ //引数objがItem型かどうかをチェックする・・(1) if(!(obj instanceof Item)){ return false; } //Item型に強制変換(キャスト)する・・(2) Item item = (Item)obj; //名前が異なる場合はfalse if( !item.name.equals(this.name) ){ return false; } //価格が異なる場合はfalse if( item.price != this.price ){ return false; } //名前,価格ともに一致していたらtrue return true; } :
コメント(1)について説明します。equalsメソッドの引数はObject型です。そのため名前と価格を比較する前に、そのインスタンスがItem型のインスタンスであるかを調べる必要があります。あるインスタンスが何型か?を調べるために用いられるのがinstanceof演算子です。次のように使用します。
変数名 instanceof 型名
プログラム中のobj instanceof Item
で、引数objがItem型かどうかを調べています。Item型でなければ「同値」とはみなさないのでfalse
を返しています。
コメント(2)について説明します。instanceof演算子を使って、objがItem型であることが確認できたら、引数objの指すインスタンスをItem型として扱えるようにします。Item型として扱うためには、型変換を行ったうえでItem型の変数に代入する 必要があります。Item item = (Item)obj;
の部分がその記述になります。キャスト演算子を使ってobjをItem型に変換し、変数itemに代入しています。
以降は、変数itemが示すインスタンスと自身のインスタンスの名前と価格が一致しているかどうかをチェックしています。
次にメインクラスを作成して、動作を確認します。プログラムは次のとおりです。
プログラム例(Main.java)
public class Main{ public static void main(String[] args){ Item i1 = new Item("りんご",200); Item i2 = i1; Item i3 = new Item("りんご",150); Item i4 = new Item("りんご",200); System.out.println(i1.equals(i2)); System.out.println(i1.equals(i3)); System.out.println(i1.equals(i4)); } }
コンパイル・実行すると、次の結果となります。
true false false
mainメソッドで定義されているi1
~i4
の4つの変数は、それぞれ下図のようにインスタンスを指し示しています。
各System.out.println内のequalsメソッドの動作はそれぞれ次の結果となります。
- i1とi2を比較、同じインスタンスを指示しているため、名前と価格が一致する。 → true
- i1とi3を比較、名前は一致するが価格は異なる。 → false
- i1とi4を比較、インスタンスは異なるが、名前も価格も一致する。 → true
Item型における「同値」を定義することができました。
17-2 Object型の活用
次に2つ目のメリットとして挙げた「すべてのクラスをObject型として扱うことができる。」について説明します。
Objectクラスはすべてのクラスの親クラスですので、たとえばObject型の配列を定義することでどのようなインスタンスでも代入できる配列を作り、まとめて操作することも可能となります。プログラム例を示します。
(Main.java)
import java.util.Date; public class Main{ public static void main(String[] args){ Object[] objs = new Object[3]; objs[0] = new String("Hello"); objs[1] = new Item("りんご",200); objs[2] = new Date();//(1) for(int i = 0; i < objs.length; i++){ System.out.println(objs[i]); } } }
コメント(1)で使われているDateクラスは日付情報を保持するクラスで、クラスライブラリで用意されているものです。また、Itemクラスは17-1-2の例のように、toStringメソッドがオーバーライドされているものとします。
実行結果
Hello 商品名:りんご 価格:200 Wed Jul 17 16:04:17 GMT+09:00 2024
配列objsの各要素をSystem.out.println
に引数として渡すことで、それぞれの型に適した表示を行っています。最終行の表示は、Dateクラスによる現在日時の表示です。
このようにObjectクラスがあることで、すべてのインスタンスをObject型として扱うことができます。クラスライブラリに含まれるクラスの中には、このことを利用した多態性の恩恵を受けているクラスが多くあります。