我们知道HashSet中不能存放重复元素,但是其内部是怎么保证元素不重复的呢?下面从源码去看看。
打开HashSet源码,发现其内部维护了一个HashMap.
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
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<>();
}
...
}
想知道为什么HashSet不能存放重复对象,那么第一步当然是看它的add方法怎么进行的判重,代码如下:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
这里我们还没有详细讲解HashMap的原理,但是可以先告诉大家一个结论。HashMap的key是不能重复的,而这里HashSet的元素又是作为了map的key,当然也不能重复了。
那么HashMap判断是否是重复对象的依据又是什么呢?通过查看HashMap源码得到其中最关键的一句:
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
原来是调用了对象的hashCode和equals方法进行的判断,所以又得出一个结论:若要将对象存放到HashSet中并保证对象不重复,应根据实际情况将对象的hashCode方法和equals方法进行重写。
通过重写Set元素类型的hashCode和equals方法来保证Set不添加重复元素。判断的次序是先判断hashCode是否相同,再判断是否equals。如何以上两个判断都为true,则认为是重复的元素。
例如:
class Person{
private String name;
private String gender;
private int age;
public Person() {
}
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
Set set = new HashSet();
set.add(new Person("张三","男",18));
set.add(new Person("李四","女",20));
set.add(new Person("王五","男",19));
set.add(new Person("张三","男",18));
Iterator it = set.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
运行结果:
Person{name='王五', gender='男', age=19}
Person{name='李四', gender='女', age=20}
Person{name='张三', gender='男', age=18}
Person{name='张三', gender='男', age=18}
从上例可以看出,由于我们没有重写Person类的hashCode与equals方法,HastMap自然会认为这两个张三对象不是重复的对象,因为凡是new出来的就是不同的对象。所以给人一种假象,似乎Set中允许添加重复的对象?
通过改写Person类,重写hashCode与equals方法,如下:
class Person{
private String name;
private String gender;
private int age;
public Person() {
}
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name) &&
Objects.equals(gender, person.gender);
}
@Override
public int hashCode() {
return Objects.hash(name, gender, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
Set set = new HashSet();
set.add(new Person("张三","男",18));
set.add(new Person("李四","女",20));
set.add(new Person("王五","男",19));
set.add(new Person("张三","男",18));
Iterator it = set.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
运行结果:
Person{name='张三', gender='男', age=18}
Person{name='李四', gender='女', age=20}
Person{name='王五', gender='男', age=19}
需要注意的是:==在判断是否是重复元素的次序一定是先判断hashCode是否为true,再判断equals是否为true,这个两个判断条件中只要有任何一个不成立,都会当做重复元素被添加。==
小结: