三十三、Set接口

一、Set接口介绍

学习Collection接口时,记得Collection中可以存放重复元素,也可以不存放重复元素,那么我们知道List中是可以存放重复元素的。那么不重复元素给哪里存放呢?
那就是Set接口,它里面的集合,所存储的元素就是不重复的。
查阅Set集合的API介绍,通过元素的equals方法,来判断是否为重复元素,它是个不包含重复元素的集合。Set集合取出元素的方式可以采用:迭代器、增强for
Set集合有多个子类,这里我们介绍其中的HashSet、LinkedHashSet这两个集合。

二、HashSet集合

查阅HashSet集合的API介绍:此类实现Set接口,由哈希表支持(实际上是一个 HashMap集合),其底层 HashMap 实例的默认初始容量是 16,加载因子是 0.75。
HashSet集合不能保证的迭代顺序与元素存储顺序相同
HashSet集合,采用哈希表结构存储数据,保证元素唯一性的方式依赖于:hashCode()与equals()方法。
构造方法:

hashset构造方法

常用方法:


hashset常用方法
1. HashSet集合存储数据的结构(哈希表)

什么是哈希表呢?
哈希表底层使用的也是数组机制,数组中也存放对象,而这些对象往数组中存放时的位置比较特殊,当需要把这些对象给数组中存放时,那么会根据这些对象的特有数据结合相应的算法,计算出这个对象在数组中的位置,然后把这个对象存放在数组中。而这样的数组就称为哈希数组,即就是哈希表。
当向哈希表中存放元素时,需要根据元素的特有数据结合相应的算法,这个算法其实就是Object类中的hashCode方法。由于任何对象都是Object类的子类,所以任何对象都拥有这个方法。即就是在给哈希表中存放对象时,会调用对象的hashCode方法,算出对象在表中的存放位置,这里需要注意,如果两个对象hashCode方法算出结果一样,这样现象称为哈希冲突,这时会调用对象的equals方法,比较这两个对象是不是同一个对象,如果equals方法返回的是true,那么就不会把第二个对象存放在哈希表中,如果返回的是false,就会把这个值存放在哈希表中。
总结:保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

哈希表结构存储数据原理.png
2. 重写equals()方法和 hashCode()方法

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
equals() 的作用是 用来判断两个对象是否相等,通过判断两个对象的地址是否相等(即,是否是同一个对象)来区分它们是否相等。它一般有两种使用情况:

  • 情况1,类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
  • 情况2,类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。
    讲到这里,顺便说一下java对equals()的要求。有以下几点:
    1. 对称性:如果x.equals(y)返回是"true",那么y.equals(x)也应该返回是"true"。
    2. 反射性:x.equals(x)必须返回是"true"。
    3. 类推性:如果x.equals(y)返回是"true",而且y.equals(z)返回是"true",那么z.equals(x)也应该返回是"true"。
    4. 一致性:如果x.equals(y)返回是"true",只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是"true"。
    5. 非空性,x.equals(null),永远返回是"false";x.equals(和x不同类型的对象)永远返回是"false"。

java中判断两个对象是否相等的规则:

首先,判断两个对象的hashCode是否相等
如果不相等,认为两个对象也不相等
如果相等,则判断两个对象用equals运算是否相等
如果不相等,认为两个对象也不相等
如果相等,认为两个对象相等

equals()相等的两个对象,hashcode()一定相等;
equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。

在Object类中,equls()方法和==的本质是相同的,都是判断两个引用是否指向同一对象。
java中很多类都重写了equals方法,供自己产生新的定义。如String类就重写了equals()方法,使得equals()方法的作用是比较两个字符串的内容是否相等。
实例:Items类继承自Object类,重写equals()方法,不按照父类的比较方法来比较两个对象,使得该方法能够按照程序员自己的意愿来比较两个对象。

@Items.java//解决编号没有合并问题
@Override
public int hashCode() {
    //若getId和getName相同,则hashCode一定相同
    return this.getId()+this.getName().hashCode();
}

@Override
public boolean equals(Object obj) {
    // TODO Auto-generated method stub
    if(this==obj)
    {
        return true;
    }
    if(obj instanceof Items)
    {
        Items i = (Items)obj;
        if(this.getId()==i.getId()&&this.getName().equals(i.getName()))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    else
    {
        return false;
    }
}


3. HashSet存储JavaAPI中的类型元素

给HashSet中存储JavaAPI中提供的类型元素时,不需要重写元素的hashCode和equals方法,因为这两个方法,在JavaAPI的每个类中已经重写完毕,如String类、Integer类等。

  • 创建HashSet集合,存储String对象。
public class HashSetDemo {
    public static void main(String[] args) {
        //创建HashSet对象
        HashSet hs = new HashSet();
        //给集合中添加String对象
        hs.add("zhangsan");
        hs.add("lisi");
        hs.add("wangwu");
        hs.add("zhangsan");
        //取出集合中的每个元素
        Iterator it = hs.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }
    }
}

输出结果如下,说明集合中不能存储重复元素:
wangwu
lisi
zhangsan

4. HashSet存储自定义类型元素

给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一

  • 创建自定义对象Student
public class Student {
    private String name;
    private int age;
    public Student(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    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;
    }
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }

//进行hashcode方法的重写
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

//进行equals方法的重写
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if(!(obj instanceof Student)){
            System.out.println("类型错误");
            return false;
        }
        Student other = (Student) obj;
        return this.age ==  other.age && this.name.equals(other.name);
    }
}
  • 创建HashSet集合,存储Student对象。
public class HashSetDemo {
    public static void main(String[] args) {
        //创建HashSet对象
        HashSet hs = new HashSet();
        //给集合中添加自定义对象
        hs.add(new Student("zhangsan",21));
        hs.add(new Student("lisi",22));
        hs.add(new Student("wangwu",23));
        hs.add(new Student("zhangsan",21));
        //取出集合中的每个元素
        Iterator it = hs.iterator();
        while(it.hasNext()){
            Student s = (Student)it.next();
            System.out.println(s);
        }
    }
}

输出结果如下,说明集合中不能存储重复元素:
Student [name=lisi, age=22]
Student [name=zhangsan, age=21]
Student [name=wangwu, age=23]

三、 LinkedHashSet介绍

我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?
在HashSet下面有一个子类LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。

linkhashset

演示代码如下:

public class LinkedHashSetDemo {
    public static void main(String[] args) {
        Set set = new LinkedHashSet();
        set.add("bbb");
        set.add("aaa");
        set.add("abc");
        set.add("bbc");
Iterator it = set.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

输出结果如下,LinkedHashSet集合保证元素的存入和取出的顺序:
bbb
aaa
abc
bbc

注意,LinkedHashSet不是同步的,即线程不安全。如果需要它变成线程安全的,则需用Collections.synchronizedSet方法来“包装”该 set。最好在创建时完成这一操作,以防止意外的非同步访问:
Set s = Collections.synchronizedSet(new LinkedHashSet(...));

四、判断结合元素是否重复的原理

1. ArrayList的contains方法判断元素是否重复原理
contains.png

ArrayList的contains方法会使用调用方法时,传入的元素的equals方法依次与集合中的旧元素所比较,从而根据返回的布尔值判断是否有重复元素。此时,当ArrayList存放自定义类型时,如果自定义类型未重写equals方法,判断是否重复的依据是地址值,所以如果想根据内容判断是否为重复元素,需要重写元素的equals方法。

2. HashSet的add/contains等方法判断元素是否重复原理
contains2.png

Set集合不能存放重复元素,其添加方法在添加时会判断是否有重复元素,有重复不添加,没重复则添加。
HashSet集合由于是无序的,其判断唯一的依据是元素类型的hashCode与equals方法的返回结果。规则如下:
先判断新元素与集合内已经有的旧元素的HashCode值

  • 如果不同,说明是不同元素,添加到集合。
  • 如果相同,再判断equals比较结果。返回true则相同元素;返回false则不同元素,添加到集合。

所以,使用HashSet存储自定义类型,如果没有重写该类的hashCode与equals方法,则判断重复时,使用的是地址值,如果想通过内容比较元素是否相同,需要重写该元素类的hashcode与equals方法。

你可能感兴趣的:(三十三、Set接口)