题目
已知 Person 类按照 id 和 name 重写了 hashCode 和 equals 方法,请问下面的代码输出什么?
请尝试自己分析后再看答案噢!
public class Work {
public static void main(String[] args) {
HashSet<Person> set = new HashSet<>();
Person p1 = new Person(1001, "AA");
Person p2 = new Person(1002, "BB");
set.add(p1);
set.add(p2);
p1.name = "CC";
set.remove(p1);
System.out.println(set);
set.add(new Person(1001, "CC"));
System.out.println(set);
set.add(new Person(1001, "AA"));
System.out.println(set);
}
}
class Person {
public int id;
public String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return id == person.id &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
解析
先上答案
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
注释解析
public static void main(String[] args) {
HashSet<Person> set = new HashSet<>();
Person p1 = new Person(1001, "AA");
Person p2 = new Person(1002, "BB");
set.add(p1);
set.add(p2);
p1.name = "CC";
// 由于 Person 类是根据 id 和 name 重写 hashCode() 和 equals() 方法的
// 所以修改了 p1 的 name 属性会重新调用 hashCode 计算 hash 值,计算出来的数组下标可能与原来的不同,所以并不会删除 p1
// 即使计算出来的数组下标与原来的相同,也会因为 equals 返回 false 所以不会删除 p1
set.remove(p1);
System.out.println(set); // HashSet 中存在 2 个元素
// 不存在相同的 Person 对象,所以可以成功添加到 HashSet 中
set.add(new Person(1001, "CC"));
System.out.println(set); // HashSet 中存在 3 个元素
// 此处的 Person 对象计算出来的索引位置与 p1 相同,但是由于 name 属性不同,所以 equals 不同,同样会加入到 HashSet 中
// 该 Person 对象存放在链表上
set.add(new Person(1001, "AA"));
System.out.println(set); // HashSet 中存在 4 个元素
}
总结
对hashCode()
和equals()
方法的理解不够深刻;对于一些常用的方法,不仅要知道这个API 的功能是做什么的,最好还要理解其内部的实现逻辑是怎样的。
在执行 remove
方法时,会重新计算 p1 的 hash 值,进行定位和删除,而定位取决于 hashCode ,name 属性发生了变化,又会导致 hashCode 发生变化,所以定位的结果也会发生变化,这是不会删除 p1 元素的原因。