← 返回首页
JavaSE系列教程(四十二)
发表时间:2020-01-31 15:57:20
讲解java集合框架之HashSet如何保证不添加重复元素?

我们知道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,这个两个判断条件中只要有任何一个不成立,都会当做重复元素被添加。==

小结:

  1. HashSet内部维护了一个HashMap,HashMap的key是不能重复的,而这里HashSet的元素又是作为了map的key,当然也不能重复了。
  2. HashMap又是通过调用了对象的hashCode和equals方法进行的判断,判断是否是重复元素的次序一定是先判断hashCode是否为true,再判断equals是否为true,这个两个判断条件中只要有任何一个不成立,都会当做重复元素被添加。
  3. 若要将对象存放到HashSet中并保证对象不重复,应根据实际情况将对象的hashCode方法和equals方法进行重写。