一、需求描述
- 使用SQL语句关联三个表后,在Debug测试的过程中,获取数据库中的记录,发现有好几条记录是重复的。经过问题的排查,导致关联的结果出现重复,是因为其中一个表中含有多条重复的历史记录。
- 需要有一种办法将返回的数据进行条件过滤,将重复的记录只保持一条,所以想到了Set集合。
二、补充
- Set集合的特点(其中2点):
(1)集合元素不重复
(2)集合元素无序 - 底层原理简述
HashSet底层是HashMap实现,value固定为一个自定义对象,使用key保证集合元素的唯一性(原理:确保元素唯一性的两个方法,hashCode()和equals()方法。当调用add()方法向集合存入对象时,先比较此对象与原有对象的哈希值是否一样,如果不一样,直接存入集合,如果哈希值一样,就会继续比较这两个对象是否为同一个对象,就会调用equals()方法)总之,只有HashCode的相同时,才会调用equals()方法。但它不保证集合元素的顺序(HashSet底层HashMap一定无序)
三、去重过程
//主函数如下所示:
public static void main(String[] args) {
List<Staff> list = new ArrayList<>();
list.add(new Staff(1,"李华","lihua@163.com",111,"13115448748","隔壁小王","人工智能系统"));
list.add(new Staff(1,"李华","lihua@163.com",111,"13115448748","隔壁小王","人工智能系统"));
Set<Staff> set = new HashSet<>(list);
//set.addAll(list);
List<Staff> list1 = new ArrayList<>(set);
System.out.println(list1.size());
}
//最初的员工实体类
package com.ml.entry;
public class Staff {
private Integer managerId;
private String manager;
private String manager_email;
private Integer staffId;
private String staffNum;
private String staffName;
private String sysName;
public Integer getManagerId() {
return managerId;
}
public void setManagerId(Integer managerId) {
this.managerId = managerId;
}
public String getManager() {
return manager;
}
public void setManager(String manager) {
this.manager = manager;
}
public String getManager_email() {
return manager_email;
}
public void setManager_email(String manager_email) {
this.manager_email = manager_email;
}
public Integer getStaffId() {
return staffId;
}
public void setStaffId(Integer staffId) {
this.staffId = staffId;
}
public String getStaffNum() {
return staffNum;
}
public void setStaffNum(String staffNum) {
this.staffNum = staffNum;
}
public String getStaffName() {
return staffName;
}
public void setStaffName(String staffName) {
this.staffName = staffName;
}
public String getSysName() {
return sysName;
}
public void setSysName(String sysName) {
this.sysName = sysName;
}
@Override
public String toString() {
return "Staff{" +
"managerId=" + managerId +
", manager='" + manager + '\'' +
", manager_email='" + manager_email + '\'' +
", staffId=" + staffId +
", staffNum='" + staffNum + '\'' +
", staffName='" + staffName + '\'' +
", sysName='" + sysName + '\'' +
'}';
}
}
//修改之后的员工实体类
package com.ml.entry;
public class Staff {
private Integer managerId;
private String manager;
private String manager_email;
private Integer staffId;
private String staffNum;
private String staffName;
private String sysName;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Staff staff = (Staff) o;
return Objects.equals(managerId, staff.managerId) &&
Objects.equals(manager, staff.manager) &&
Objects.equals(manager_email, staff.manager_email) &&
Objects.equals(staffId, staff.staffId) &&
Objects.equals(staffNum, staff.staffNum) &&
Objects.equals(staffName, staff.staffName) &&
Objects.equals(sysName, staff.sysName);
}
@Override
public int hashCode() {
return Objects.hash(managerId, manager, manager_email, staffId, staffNum, staffName, sysName);
}
public Integer getManagerId() {
return managerId;
}
public void setManagerId(Integer managerId) {
this.managerId = managerId;
}
public String getManager() {
return manager;
}
public void setManager(String manager) {
this.manager = manager;
}
public String getManager_email() {
return manager_email;
}
public void setManager_email(String manager_email) {
this.manager_email = manager_email;
}
public Integer getStaffId() {
return staffId;
}
public void setStaffId(Integer staffId) {
this.staffId = staffId;
}
public String getStaffNum() {
return staffNum;
}
public void setStaffNum(String staffNum) {
this.staffNum = staffNum;
}
public String getStaffName() {
return staffName;
}
public void setStaffName(String staffName) {
this.staffName = staffName;
}
public String getSysName() {
return sysName;
}
public void setSysName(String sysName) {
this.sysName = sysName;
}
@Override
public String toString() {
return "Staff{" +
"managerId=" + managerId +
", manager='" + manager + '\'' +
", manager_email='" + manager_email + '\'' +
", staffId=" + staffId +
", staffNum='" + staffNum + '\'' +
", staffName='" + staffName + '\'' +
", sysName='" + sysName + '\'' +
'}';
}
}
运行主函数测试结果显示,通过Set集合自动去重之后,集合中还是含有两个对象,并没有去重。
通过仔细的分析,问题原来出现在员工实体类对象中。原来对于自定义的实体类,若想通过Set集合对List集合达到去重效果的话,需要在自定义的实体类中重写hashCode()和equals()方法。
四、过程分析
-
首先我们看一下HashSet的具体实现,就拿add()方法来分析一下。
方框1处分析: -
HashSet集合对象是如何判断数据元素是否重复的呢?
(1)首先会检查待存对象key的hashCode值是否与集合中已有元素对象hashCode值相同,如果hashCode不同则表示不重复, 如果hashCode相同那么会再调用equals方法进一步检查,equals返回true表示对象已存在,否则表示不存在。
注意: 在Java中,hashCode值是由存储对象的地址所确定的,每一个地址对应一个hashCode值。 -
所以回到我们问题初始的地方,似乎就能找到答案了,当我们在向Staff对象添加两个相同对象值的时候,为什么经过HashSet处理之后,理应去重,但是实际输出结果却未去重。
-
原因就在于我们通过使用new Staff();对象创建实际是创建了两个不同的对象,也就对应了两个地址进行存储对象,所以Ha shSet在if判断的时候,对于HashCode不同,就返回了false,证明集合中是两个不同的对象值,这样就使得最后集合中并没有去重。
-
所以通过这个小的案例,我们可以明白一个道理。当我们想要对集合对象达到去重效果的话,我们需要在自定义的Staff类中重写hashCode方法,让id值相同的Staff对象的hashCode值相同,这样才能让id相同的对象通过上面判断条件的第一关。
-
两个对象的Student对象就会在4.1方框1的if判断的p.hash == hash返回true而进入下一个判断条件,而由于(k = p.key) == key比较的是两个对象的地址,所以自然是false,接着就进入key != null && key.equals(k)的判断,而由于Staff类中没有equals方法,所以这里调用的是原生的Object类中equals方法,而通过查找我们发现,Object类中的equals方法比对的仍然是对象的地址,这样返回的结果还是false,那么这样肯定是不行的,所以我们这里就需要对Staff类做一些处理;
-
解决办法就是在Staff类中重写equals方法,来达到将这两个Student对象判断为一个对象的目的,下图就是我在Staff类中重写的equals方法:
注意:在重写equals方法的时候,需要考虑到调用这个方法的并不一定是Staff类所创建的对象,这时就需要添加一个if语句去判断调用这个方法的对象是否为Staff类的实例,如果是,那么我们就将它的元素值与集合中已有对象的元素值做对比。
五、过程总结
- HashSet达到去重的二要素:
(1)重写HashCode方法
(2)重写equals方法 - 两个方法重写的目的:
(1)重写HashCode方法目的是保证两个对象之间的HashCode相同
(2)重写equals方法目的是保证两个对象为一个相同对象