Point型インスタンスを利用した際に、equalsメソッドと toStringメソッドを利用してきました。ここでは、Dogクラスや Catクラスなど自作したクラスについての、これらのメソッドの挙動について解説したいと思います。
念のため、equalsメソッドと toStringメソッドについて再確認しておきます。
equalsメソッドも toStringメソッドも、(Point型などの)既存のクラスのものを利用する場合には、正しく動作するように実装されています。ところが、(Dog型などの)自作のクラスのものを利用する場合には、自ら実装をする必要があります。
ここではそれらについて、実装しなかった場合にどうなるか調べてみることと、具体的な実装方法について解説していくことにします。
ソースコードは以下の通り。
K301/Dog.java
/** * 犬を表すクラスです。 */ public class Dog { /** * 名前。 */ private String name; /** * 鳴く回数。 */ private int cryCount; /** * コンストラクタ * @param name 名前 */ public Dog(String name) { this(name, 1); } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public Dog(String name, int cryCount) { this.setName(name); this.setCryCount(cryCount); } /** * 名前を設定します。 * @param name 名前 */ public void setName(String name) { this.name = name; } /** * 鳴く回数を設定します。 * @param cryCount 鳴く回数 */ public void setCryCount(int cryCount) { this.cryCount = cryCount; } /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ワン"); } System.out.println("」"); } /** * お座りします。 */ public void sitDown() { System.out.println(this.name + "は座りました。"); } }
K301/K301.java
/** * equalsメソッドを利用します。 */ public class K301 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { // インスタンスを作成 Dog dog1 = new Dog("ポチ"); Dog dog2 = new Dog("クロ", 5); Dog dog3 = new Dog("クロ", 5); // 犬のインスタンスメソッドを実行 dog1.cry(); dog1.sitDown(); dog2.cry(); dog3.cry(); // Dogインスタンスの比較 if(dog2.equals(dog3)) { System.out.println("インスタンスの内容は同じ。"); } else { System.out.println("インスタンスの内容は異なる。"); } } }
実行結果は以下の通り。
ポチ「ワン」 ポチは座りました。 クロ「ワンワンワンワンワン」 クロ「ワンワンワンワンワン」 インスタンスの内容は異なる。
変数 dog2と 変数 dog3の参照するインスタンスは異なりますが、dog2の参照先インスタンスの内容と、dog3の参照先インスタンスの内容は同じです(ともに、名前は「クロ」で鳴く回数は 5回です)。インスタンスの内容は同じであるはずです。ところが、equalsメソッドの結果は falseとなってしまい「インスタンスの内容は異なる」となってしまっています。
これは、自作したクラスについては「デフォルトでは、equalsメソッドの挙動は ==演算子の挙動と同じ」というルールになっているためです。つまり、せっかく equalsメソッドを使用しているのに、==演算子を使用したときと同じ扱いになっているということになります。自作クラスについて equalsメソッドを利用する際には、自分で equalsメソッドを実装(オーバーライド)する必要があります。
その例を次に見ていきます。
equalsメソッドを実装(オーバーライド)した例を次に示します。既存の equalsメソッドの内容を上書きすることになり、これをオーバーライドと言います(オーバーロードと間違えないようにしてください)。オーバーライドの詳細については、次の章にていろいろ解説していきたいと思います。
ソースコードは以下の通り。
K302/Dog.java
/** * 犬を表すクラスです。 */ public class Dog { /** * 名前。 */ private String name; /** * 鳴く回数。 */ private int cryCount; /** * コンストラクタ * @param name 名前 */ public Dog(String name) { this(name, 1); } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public Dog(String name, int cryCount) { this.setName(name); this.setCryCount(cryCount); } /** * 名前を設定します。 * @param name 名前 */ public void setName(String name) { this.name = name; } /** * 鳴く回数を設定します。 * @param cryCount 鳴く回数 */ public void setCryCount(int cryCount) { this.cryCount = cryCount; } /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ワン"); } System.out.println("」"); } /** * お座りします。 */ public void sitDown() { System.out.println(this.name + "は座りました。"); } @Override public boolean equals(Object obj) { if(this == obj) { return true; } else if(obj == null) { return false; } else if(this.getClass() != obj.getClass()) { return false; } else { // 参照型のキャスト Dog other = (Dog)obj; // 名前と鳴く回数が同じかどうか判定する if(this.name.equals(other.name) && this.cryCount == other.cryCount) { return true; } else { return false; } } } }
K302/K302.java
/** * equalsメソッドを利用します。 */ public class K302 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { // インスタンスを作成 Dog dog1 = new Dog("ポチ"); Dog dog2 = new Dog("クロ", 5); Dog dog3 = new Dog("クロ", 5); // 犬のインスタンスメソッドを実行 dog1.cry(); dog1.sitDown(); dog2.cry(); dog3.cry(); // Dogインスタンスの比較 if(dog2.equals(dog3)) { System.out.println("インスタンスの内容は同じ。"); } else { System.out.println("インスタンスの内容は異なる。"); } } }
実行結果は以下の通り。
ポチ「ワン」 ポチは座りました。 クロ「ワンワンワンワンワン」 クロ「ワンワンワンワンワン」 インスタンスの内容は同じ。
まず、実行結果に注目してみます。確かに「インスタンスの内容は同じ。」と出力されることができました。異なるインスタンスを参照したとしても、インスタンスの内容が同じだったならば「等しい」とみなす、equalsメソッドらしい動きになりました。
equalsメソッドの中身はとても複雑です。実際のところ、本ウェブサイトで扱わない範囲を含んでいます。ただ、Dogクラスの 68行目から 74行目までは完全な定型文なので、中身を理解する必要はありません。分からないままコピペで利用して大丈夫です。そして、76行目が参照型のキャストになります。参照型のキャストの意味は基本型とキャストとまったく異なります。これについては後々説明しますが、訳分からないまま使用して大丈夫です。
最も肝心なのが 79行目で、ここで名前の比較と鳴く回数の比較を行っています。名前も鳴く回数も一致した場合には true、一方でも等しくない場合には falseとなるように記述されていることが分かるでしょうか。
Dogクラスの equalsメソッドをオーバーライドすることで、メインメソッド側の記述はそのままで上手く動作するようになったことが理解できるでしょうか。
なお、equalsメソッドの実装(オーバーライド)はこのように記述量が多く手間がかかりますので、すべての自作クラスに実装するのではなく、必要に迫られた最小限のクラスについて実装する形で、特段問題ないのではないかなと、個人的には思います。
続いて、toStringメソッドについて、デフォルトの動作を見てみます。
ソースコードは以下の通り。
K303/Dog.java
/** * 犬を表すクラスです。 */ public class Dog { /** * 名前。 */ private String name; /** * 鳴く回数。 */ private int cryCount; /** * コンストラクタ * @param name 名前 */ public Dog(String name) { this(name, 1); } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public Dog(String name, int cryCount) { this.setName(name); this.setCryCount(cryCount); } /** * 名前を設定します。 * @param name 名前 */ public void setName(String name) { this.name = name; } /** * 鳴く回数を設定します。 * @param cryCount 鳴く回数 */ public void setCryCount(int cryCount) { this.cryCount = cryCount; } /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ワン"); } System.out.println("」"); } /** * お座りします。 */ public void sitDown() { System.out.println(this.name + "は座りました。"); } }
K303/K303.java
/** * toStringメソッドを利用します。 */ public class K303 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { // インスタンスを作成 Dog dog1 = new Dog("ポチ"); Dog dog2 = new Dog("クロ", 5); // 犬のインスタンスメソッドを実行 dog1.cry(); dog1.sitDown(); dog2.cry(); // Dogインスタンスの内容を表示 System.out.println("変数 dog1の参照先インスタンスの内容は " + dog1); System.out.println("変数 dog2の参照先インスタンスの内容は " + dog2); } }
実行結果の例は以下の通り。
ポチ「ワン」 ポチは座りました。 クロ「ワンワンワンワンワン」 変数 dog1の参照先インスタンスの内容は Dog@2073b879 変数 dog2の参照先インスタンスの内容は Dog@d542094
よく分からない文字列が出力されました。これは、クラス名と(コンピュータのメモリの)番地を表しています。ただ、これではインスタンスの内容がまったく分かりませんので、いろいろと調べる際には不都合です。そこで、次の例で toStringメソッドをオーバーライドしていきます。
toStringメソッドをオーバーライドした例は以下の通り。
ソースコードは以下の通り。
K304/Dog.java
/** * 犬を表すクラスです。 */ public class Dog { /** * 名前。 */ private String name; /** * 鳴く回数。 */ private int cryCount; /** * コンストラクタ * @param name 名前 */ public Dog(String name) { this(name, 1); } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public Dog(String name, int cryCount) { this.setName(name); this.setCryCount(cryCount); } /** * 名前を設定します。 * @param name 名前 */ public void setName(String name) { this.name = name; } /** * 鳴く回数を設定します。 * @param cryCount 鳴く回数 */ public void setCryCount(int cryCount) { this.cryCount = cryCount; } /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ワン"); } System.out.println("」"); } /** * お座りします。 */ public void sitDown() { System.out.println(this.name + "は座りました。"); } @Override public String toString() { return "[" + this.name + ":" + this.cryCount + "]"; } }
K304/K304.java
/** * toStringメソッドを利用します。 */ public class K304 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { // インスタンスを作成 Dog dog1 = new Dog("ポチ"); Dog dog2 = new Dog("クロ", 5); // 犬のインスタンスメソッドを実行 dog1.cry(); dog1.sitDown(); dog2.cry(); // Dogインスタンスの内容を表示 System.out.println("変数 dog1の参照先インスタンスの内容は " + dog1); System.out.println("変数 dog2の参照先インスタンスの内容は " + dog2); } }
実行結果は以下の通り。
ポチ「ワン」 ポチは座りました。 クロ「ワンワンワンワンワン」 変数 dog1の参照先インスタンスの内容は [ポチ:1] 変数 dog2の参照先インスタンスの内容は [クロ:5]
インスタンスの内容がとても分かりやすく表現されています。
toStringメソッドの実装は(equalsメソッドと異なって)シンプルで、ただ文字列を返すように記述するだけです。
メソッドをオーバーライドする場合にはアノテーションを利用すると便利です。メソッドの前に記述する「@Override」は、アノテーションの一種です。
@Override public String toString() {
アノテーションを記述しなくてもプログラムは問題なく動作します。ただ、アノテーションを記述しないときにメソッド名を綴りミスすると、(ビルドエラーにならない場合があり)なかなかそのミスに気づきづらいという問題があります。アノテーションを記述しておくと、メソッド名を綴りミスした場合、必ずビルドエラーになるため、すぐミスに気づくことができます。
@Override public String toStrong() { // ビルドエラー
本ウェブサイトでは、アノテーションの一種「@Override」は一切省略せず、すべて記述することにします。
なお、アノテーションは「@Override」以外にも幾つかあるのですが、本ウェブサイトでは「@Override」だけを使用します。