set去重原理
大家都知道,set()自带天然去重,例如
Set<String> set = new HashSet<String>();
set.add("aaa");
set.add("bbb");
set.add("aaa");
set.add("ccc");
System.out.println(set);
它的输出结果为:[aaa, ccc, bbb]
可以看到:1.去重了;2.输出无序
那么,set()是如何去重的呢
自定义一个类
为了贴合实际的开发需求,我们常需要自定义数据结构。拿通用示例 Student 来说。
class Student{
private String name;
private int age;
private int sid;
public Student(String name, int age, int sid){
this.name = name;
this.age = age;
this.sid = sid;
}
}
现在,我们实例两个 Student 对象,分别是 stu1 和 stu2,其名字 name,年龄 age,学号 sid 相同。现实生活中,可以认为这两个学生是同一人。
测试
public static void main(String[] args) {
Set<Student> set = new HashSet<Student>();
Student stu1 = new Student("jiang", 20, 121);
Student stu2 = new Student("jiang", 20, 122);
set.add(stu1);
set.add(stu2);
System.out.println(stu1 == stu2);
System.out.println(set);
}
输出
false
[Student@1218025c, Student@548c4f57]
可见,对于new出来的两个对象 stu1 和 stu2 set并没有做去重
发现
跟踪add()发现
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element <tt>e</tt> to this set if
* this set contains no element <tt>e2</tt> such that
* <tt>(e==null ? e2==null : e.equals(e2))</tt>.
* If this set already contains the element, the call leaves the set
* unchanged and returns <tt>false</tt>.
*
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
点进去看到
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
1.现在我们看到了,map是HashMap类型,Hash类型是散列的,所以无序。
2.因为new了一个对象名为PERSENT的Object对象。以传入的e作为key值,PERSENT作为value来存储到map中,如果key值相同,将会覆盖,这就是set为什么能去重的原因(key相同会覆盖)。
改造
我们在 Student 类中重写 equals方法 和 hashCode方法
@Override
public boolean equals(Object obj) {
Student tmp = (Student)obj;
if(tmp.getAge() == this.age && tmp.getName() == this.name && tmp.getName() == this.name){
return true;
}else{
return false;
}
}
@Override
public int hashCode() {
return this.getName().hashCode()+this.age;
}
然后重新打印输出set
[Student@606f04f]
我们发现,它只有一个了,完成了去重操作
这两个方法少些一个都不行
去重原理
经过前面一步步推导,我们得到了set()去重的原理
- set() 函数中会先调用对象的
hashCode()
方法,获取 hash 结果; - 如果 hash 结果相同,用比较操作符
==
(也就是调用函数equals()
)判断二者的值是否相等; - 如果都相等,去重;否则,set() 认为二者不同,两个都保留到结果中。