当集合中的对象要去重时,为什么要重写hashCode和equals方法
当集合中存放的是对象时,如果要去重则需要重写hashCode和equals方法。
网上这类文章博客有很多,写一下自己的理解。
public class Student{
private long id;
private String name;
private int age;
private String address;
public Student(){}
public Student(long id, String name, int age, String address) {
super();
this.id = id;
this.name = name;
this.age = age;
this.address = address;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", age=" + age + ", address=" + address + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(id, student.id) &&
Objects.equals(name, student.name) &&
Objects.equals(address, student.address);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age, address);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public static void main(String[] args){
Student s1 = new Student(1L,"肖战",15,"浙江");
Student s2 = new Student(2L,"王一博",15,"湖北");
Student s3 = new Student(3L,"杨紫",17,"北京");
Student s4 = new Student(4L,"李现",17,"浙江");
Student s5 = new Student(1L,"肖战",15,"浙江");
List<Student> students = new ArrayList<>(4);
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
students.add(s5);
List<Student> streamStudents = testDistinct1(students);
streamStudents.forEach(System.out::println);
}
/**
* 集合去重(对象的去重需要重写hashCode和equals方法)
* @param tudents
* @return
*/
private static List<Student> testDistinct1(List<Student> students){
return students.stream().distinct().collect(Collectors.toList());
}
}
如上:创建了一个Student类并重写了他的equals和hashcode方法,执行下main方法,执行结果:
Student [id=1, name=肖战, age=15, address=浙江]
Student [id=2, name=王一博, age=15, address=湖北]
Student [id=3, name=杨紫, age=17, address=北京]
Student [id=4, name=李现, age=17, address=浙江]
结果集被成功去重了。
这时我们将重写的hashCode和equals方法去掉或者只去掉其中一个会发现都不会成功去重
原因是:
我们的对象默认继承的是Object类,原始的equals方法比对的是值,也就是堆内存值的地址,当我们创建student s5的时候,看上去是与student s1是一模一样的,但他们的引用地址不一样,所以存在堆内存地址中是两个值。看一下equals的源码
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
可以看到他既做了a==b的判断也做了equals的判断,既判断他两个对象是否相同也判断他两个值引用地址是否相同,那我们重写的逻辑就是判断对象里的值是否完全相同,一般用工具自带的生成equals方法就能实现-。- 还真的是很方便呢。
那为什么重写了equals方法还要重写hashCode方法才能使集合去重成功呢?
先看一下hashCode的源码
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
可以看到传参是一个对象数组,返回值是一个int,也就是我们说的hash值,每个值引用的hash值是不一样的,hash值不一样也不能判定s1和s5是一模一样的。
引申出的一个问题:hashcode里的31是什么,这个乘积的作用是什么,为什么是31而不是30或是29或其他的数
参考文章:https://www.cnblogs.com/0813lichenyu/p/8367103.html
看来选择31作为乘积还是因为性能原因
再回到我们的问题上,已经知道不重写hashcode方法会使我们的的去重不生效,那应该如何重写呢,其实跟equals一样,只要对对象中每个值的hash值做判断就可以了,如果全都一样则对象的hash值也是一样的。也可以通过工具自动生成...
好了 这下我们已经知道怎么重写hashcode和equals方法了,回过头我们再去看一下官方jdk api上对hashcode和equals的描述
其实官方文档已经说得很清楚了,重写equals方法时最好同时重写hashcode方法QwQ。
好了,这就是为什么当集合中的对象要去重时,要重写hashCode和equals方法的原因