==、equals() および hashcode()
「==」
説明の前に、どうやってこの記号に出会ったかというと、==
比較するときによく使う==
、基本クラスのデータとしてよく使われる記号です。
==
演算子は通常、基本データ型 (int、long、double など) と列挙型の値の同一性を比較するために使用されますが、オブジェクト型の場合、演算子はそれらの参照が等しいかどうかを比較するために使用されます。つまり、それらが同じ==
オブジェクトを指しているかどうかです。
質問について考えてみましょう。オブジェクトの比較に通常 == を使用しないのはなぜですか。2 つのオブジェクトの参照が同じである場合、つまり、同じオブジェクトを指している場合、演算子を使用してそれらの参照を比較すると true が返されます==
。なぜなら、それらは同じオブジェクトを指しているからです。この場合、2つのオブジェクトは同じオブジェクトであるため、当然その内容は同じになります。
では、オブジェクトを比較するために直接 を使用せず==
、問題がある場合は他のメソッドを使用してみてはいかがでしょうか。
理由:オブジェクトを比較するのは、参照が同じかどうかではなく、内部のプロパティが同じかどうかを確認するためです。
この場合、==
演算子を使用して 2 つのオブジェクトの参照を比較しても、それらの内容が同じかどうかはわかりません。これは、2 つの異なるオブジェクトが同じプロパティ値を持つ可能性がありますが、それらの参照が異なるためです。したがって、オブジェクトが等しいかどうかを比較する場合、演算子equals()
を使用するのではなく、オブジェクトのセマンティクスに従ってメソッドを実装する必要があります==
。
public class Main {
public static void main(String[] args) {
int a = 5;
int b = 5;
int c = 6;
System.out.println(a==b); // true
System.out.println(a==c); // false
Person p1 = new Person("hhh", 15);
Person p2 = new Person("hhh", 15);
// 属性相同,但引用不同。按照常理应该被认作是相同的。实际却是不同的
System.out.println(p1 == p2); // false
}
}
class Person{
String name;
Integer age;
Person(String name, Integer age){
this.age = age;
this.name = name;
}
}
等しい() メソッド
equals()
メソッドはObject
クラス内のメソッドです。これはアドレスの比較のみであるため、2 つのクラスが同じクラスであるかどうかのみを比較できますが、属性が同じかどうかは比較できません。したがって、オブジェクトを比較するときは通常、メソッドをオーバーライドしますequals()
。
public class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
// getters and setters
@Override
public boolean equals(Object obj) {
return (this == obj);
}
}
クラスの属性に基本データ型と参照データ型が含まれる場合、これらの属性値は同じですが、参照アドレスは必ずしも同じではありません。equals()
この時点で、正しい結果を得るには、オブジェクトのセマンティクスに従ってメソッドを書き直して比較する必要があります。
列表中的使用
在Java中,列表类(如List
)通常用来存储一组对象,当我们需要在列表中查找某个对象时,通常需要调用列表的contains()
方法或indexOf()
方法。这些方法会使用对象的equals()
方法来比较对象是否相等。
它是通过比较对象的属性值从而去列表中查找。
public class Main {
public static void main(String[] args) {
List<String> list = List.of("A", "B", "C");
// 下面的例子中即使对象的地址不同,但是属性值相同,因此在列表中能查到
System.out.println(list.contains(new String("C"))); // true
System.out.println(list.indexOf(new String("C"))); // 2
}
}
在hash结构的表中使用
map
是一种键值对的映射表,map
使用一个很大的数组来存储value
,然后通过key
的hash
值来查找value
的索引
map
其实可以看作一个数组,key
的hashcode()
值可以作为数组的索引,然后根据索引去查找value
。
那究竟在什么地方使用equals()
呢?
当我们输入key
的时候,map
需要将传入的key
与当前数组中存入的key
相比较。
public class Main {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
map.put(new String("sss"),"hhh");
map.put(new String("sss"), "aaa");
// map里只有一个数据,因为String重写了equals方法,所以key属性值一样的会被覆盖
System.out.println(map.size());
HashMap<Person, String> map1 = new HashMap<>();
map1.put(new Person("xiao",15), "ok");
map1.put(new Person("xiao",15), "hao");
// map1里有两个数据,明明key是一样的,却没有覆盖
System.out.println(map1.size());
}
}
class Person{
String name;
int age;
Person(String name, int age){
this.name = name;
this.age = age;
}
}
- 添加重复的键值对:由于
Map
将不同的对象视为不同的键,如果我们使用相同类型的不同对象作为键,即使它们的属性值相同,Map
也会将它们视为不同的键,从而导致添加重复的键值对。 - 无法查找键值对:由于
Map
将不同的对象视为不同的键,如果我们使用一个对象作为键添加了一个键值对,而后又使用另一个对象作为键查找该键值对,那么Map
会认为这两个对象是不同的键,因此无法找到对应的键值对。
因此,我们需要正确重写equals()
方法。
hashcode()方法
HashMap
为什么能通过key
直接计算出value
存储的索引。相同的key
对象(使用equals()
判断时返回true
)必须要计算出相同的索引,否则,相同的key
每次取出的value
就不一定对。
map
是通过hashcode()
去寻找value
数组索引的,所以还需要为对象覆写hashcode()
方法。
public class Main {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
map.put(new String("sss"),"hhh");
map.put(new String("sss"), "aaa");
// map里只有一个数据,因为String重写了equals方法和hashcode方法,所以key属性值一样的会被覆盖
System.out.println(map.size()); // 1
HashMap<Person, String> map1 = new HashMap<>();
Person p1 = new Person("xiao", 15);
map1.put(p1, "ok");
Person p2 = new Person("xiao", 15);
map1.put(p2, "hao");
// map现在也只有一个数据了,因为两个相同属性的对象被视作同一个
System.out.println(map1.size()); // 1
}
}
class Person{
String name;
int age;
Person(String name, int age){
this.name = name;
this.age = age;
}
// 覆写equals方法
@Override
public boolean equals(Object o) {
if (o instanceof Person p) {
return this.name.equals(p.name) && this.age == p.age;
}
return false;
}
// 覆写hashcode方法
@Override
public int hashCode() {
int h = 0;
h = 31 * h + name.hashCode();
h = 31 * h + age;
return h;
}
}
map
的put
方法里是用到了hash
值的,因此不正确重写hash
值而正确重写equals
方法是没用的。
小节
比较基本数据类型或对象地址用==
, 比较对象地址使用Object
的equals
方法, 比较对象属性值使用覆写之后的equals()
.
对于list
集合数据类型,本质是数组,当存储对象数据类型的值时,重写equals方法
才能使用contains()
等需要使用equals
进行比较的方法。
对于hash table类型的数据,必须覆写equals与hashcode方法,才能正常工作。