【今回学ぶこと】
・2つの変数に入っているインスタンスを比較して、等価であるかを正しく判定できるようになること
・等価と等値の違いを理解すること
【等値とは】
完全同一の存在であることです。
つまり同じアドレスであることです。
package jp.co.mtanaka;
public class Sample1 {
public static void main(String[] args) {
Person a = new Person("かまいたち", "濱家");
Person b = a;
System.out.println(b == a);
}
}
注目するところは、aとbの2つのアドレスです。
このとき、2つのオブジェクトが同じインスタンス(アドレス)を指していればtrue,
異なるインスタンスであればfalseになります。
内容的に同じ値が入っていたとしてもインスタンスが異なればfalseとなります。
【等価とは】
2つの変数に入っているもの(インスタンス)を比較して「同じ内容」であることです。
同じアドレスを指していなくても良いです。
例えば、以下だと2つのインスタンスaとbの
combination=かまいたち、name=濱家
なので同じ内容と判断できます。
aとbは同じアドレスでなくてもよいです。
Person a = new Person("かまいたち", "濱家");
Person b = new Person("かまいたち", "濱家");
【等価判定を行うためには①】
全てのクラスがObjectクラスから継承して備えるequals()を使用します。
equals()メソッドは2つの変数に入っているもの(インスタンス)を比較して「同じ内容」であることを判定するメソッドです。
では、先ほどの例で実施してみます。
aとbのインスタンスはともに「combination=かまいたち、name=濱家」です。
でも、なぜか実施結果はfalseになります。
想定通りの結果にはなりませんでした・・・
package jp.co.mtanaka;
public class Sample1 {
public static void main(String[] args) {
Person a = new Person("かまいたち", "濱家");
Person b = new Person("かまいたち", "濱家");
System.out.println(a.equals(b));
}
}
【等価判定を行うためには②】
==を用いた等"値"判定はすぐに行えました。
これはJVMからしたら、同じアドレスかどうかを単純に比較すればよいからです。
一方、equals()を用いる等価判定は簡単ではありません。
「2つのインスタンスの何をもって同一と見なすか」という基準はクラスによって違うことがあるためです。
例えば、コンビ名の「かまいたち」と「カマイタチ」を同一の内容と判断する必要があるかもしれません。
その判断基準を持っているのは、私たち開発者であってJVMではないのです。
先ほど、同じ内容であってもeqaulsメソッドを使用した場合はfalseになりましたが、
実は、Objectクラスに宣言されているequals()の中身は「ただの等値判定」ロジックになっているためです。
要は同じアドレスかどうかを判定しているのです。
これは、equals()が呼び出されたらJVMは何かしらの結果を返さなければなりません。
ですが、「2つのインスタンスの何をもって同一とみなすか」という基準はクラスによって違うことがあるため、
「等値であれば等価である」、もう少し砕いた言い方だと、
「2つの変数のインスタンスが同じアドレスであれば同じ内容」であると言わざるを得ない苦肉の策とのことです。
※諸説あり
【equals()を使用して正しく等値判定を行うためには】
繰り返しになりますが、「何をもってインスタンス同士を等価(同じ内容)と見なしてよいか」を判断するのはそのクラスの開発者だけです。
toString()同様、自分でクラスを開発したらそのクラスのequals()をオーバーライドする必要があります。
これにより、私たちが適切と考える等価判定アルゴリズムをJVMに伝えることができます。
equals()の中身に記述する処理内容は様々ですが、定石とされる書き方を以下に記します。
package jp.co.mtanaka;
public class Sample1 {
public static void main(String[] args) {
Person a = new Person("かまいたち", "濱家");
Person b = new Person("かまいたち", "濱家");
Person c = b;
System.out.println(b == a);
System.out.println(c == b);
System.out.println(a.equals(b));
System.out.println(a);
}
}
package jp.co.mtanaka;
public class Person {
private String combination;
private String name;
public Person(String combination, String name) {
this.combination = combination;
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if(!(obj instanceof Person)) {
return false;
}
Person p = (Person) obj;
if (!this.combination.equals(p.combination)) {
return false;
}
if (!this.name.equals(p.name)) {
return false;
}
return true;
}
@Override
public String toString() {
return "お笑い芸人(コンビ名=" + this.combination + "/名前=" + this.name + "さん)";
}
}
【まとめ】
・等値判定について
==を用いれば、等値判定はすぐに行うことができます。
これはJVMからしたら、同じアドレスかどうかを単純に比較すればよいからです。
・等価判定について
「何をもってインスタンス同士を等価(同じ内容)と見なしてよいか」を判断するのはそのクラスの開発者だけです。
そのため、toString()同様、自分でクラスを開発したらそのクラスのequals()をオーバーライドする必要があります。
これにより、私たちが適切と考える等価判定アルゴリズムをJVMに伝えることができます。
また、オーバーライドには定石があるので、調べながら対応できれば良い。
ちなみに「instanceofは左辺値がnullなら常にfalseなので、その前のif文は省略することも可能です」