介绍

  • Set 接口是Collection的子接口,Set 接口没有提供额外的方法,但是比Collection接口更加严格了
  • Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个 Set 集合中,则添加操作失败
  • Set 集合支持的遍历方式和 Collection 集合一样:foreach和Iterator
  • Set的常用实现类有:HashSet、TreeSet、LinkedHashSet

11.6.1 HashSet

  • HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类
  • java.util.HashSet底层的实现其实是一个java.util.HashMap支持,然后 HashMap 的底层物理实现是一个Hash 表
  • HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能
  • HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等
  • 因此,存储到 HashSet 的元素要重写 hashCode 和 equals 方法

示例代码

  • 定义一个 Employee 类,该类包含属性:name, birthday,其中 birthday 为 MyDate类的对象;MyDate 为自定义类型,包含年、月、日属性
  • 要求 name和 birthday 一样的视为同一个员工
    1. public class Employee {
    2. private String name;
    3. private MyDate birthday;
    4. public Employee(String name, MyDate birthday) {
    5. super();
    6. this.name = name;
    7. this.birthday = birthday;
    8. }
    9. public Employee() {
    10. super();
    11. }
    12. public String getName() {
    13. return name;
    14. }
    15. public void setName(String name) {
    16. this.name = name;
    17. }
    18. public MyDate getBirthday() {
    19. return birthday;
    20. }
    21. public void setBirthday(MyDate birthday) {
    22. this.birthday = birthday;
    23. }
    24. @Override
    25. public int hashCode() {
    26. final int prime = 31;
    27. int result = 1;
    28. result = prime * result + ((birthday == null) ? 0 : birthday.hashCode());
    29. result = prime * result + ((name == null) ? 0 : name.hashCode());
    30. return result;
    31. }
    32. @Override
    33. public boolean equals(Object obj) {
    34. if (this == obj)
    35. return true;
    36. if (obj == null)
    37. return false;
    38. if (getClass() != obj.getClass())
    39. return false;
    40. Employee other = (Employee) obj;
    41. if (birthday == null) {
    42. if (other.birthday != null)
    43. return false;
    44. } else if (!birthday.equals(other.birthday))
    45. return false;
    46. if (name == null) {
    47. if (other.name != null)
    48. return false;
    49. } else if (!name.equals(other.name))
    50. return false;
    51. return true;
    52. }
    53. @Override
    54. public String toString() {
    55. return "姓名:" + name + ", 生日:" + birthday;
    56. }
    57. }
public class MyDate {
    private int year;
    private int month;
    private int day;
    public MyDate(int year, int month, int day) {
        super();
        this.year = year;
        this.month = month;
        this.day = day;
    }
    public MyDate() {
        super();
    }
    public int getYear() {
        return year;
    }
    public void setYear(int year) {
        this.year = year;
    }
    public int getMonth() {
        return month;
    }
    public void setMonth(int month) {
        this.month = month;
    }
    public int getDay() {
        return day;
    }
    public void setDay(int day) {
        this.day = day;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + day;
        result = prime * result + month;
        result = prime * result + year;
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        MyDate other = (MyDate) obj;
        if (day != other.day)
            return false;
        if (month != other.month)
            return false;
        if (year != other.year)
            return false;
        return true;
    }
    @Override
    public String toString() {
        return year + "-" + month + "-" + day;
    }
}
import java.util.HashSet;

public class TestHashSet {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        HashSet<Employee> set = new HashSet<>();
        set.add(new Employee("张三", new MyDate(1990,1,1)));
        //重复元素无法添加,因为MyDate和Employee重写了hashCode和equals方法
        set.add(new Employee("张三", new MyDate(1990,1,1)));
        set.add(new Employee("李四", new MyDate(1992,2,2)));

        for (Employee object : set) {
            System.out.println(object);
        }
    }
}

11.6.2 LinkedHashSet

LinkedHashSet 是 HashSe t的子类,它在 HashSet 的基础上,在结点中增加两个属性 before 和 after 维护了结点的前后添加顺序java.util.LinkedHashSet,它是链表和哈希表组合的一个数据存储结构LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能

LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("张三");
set.add("李四");
set.add("王五");
set.add("张三");

System.out.println("元素个数:" + set.size());
for (String name : set) {
    System.out.println(name);
}
运行结果:
元素个数:3
张三
李四
王五

11.6.2 TreeSet

底层结构:里面维护了一个 TreeMap,都是基于红黑树实现的!

特点
1、不允许重复
2、实现排序
自然排序或定制排序

如何实现去重的?

如果使用的是自然排序,则通过调用实现的 compareTo 方法
如果使用的是定制排序,则通过调用比较器的 compare 方法

如何排序?

方式一:自然排序
让待添加的元素类型实现 Comparable 接口,并重写 compareTo 方法

方式二:定制排序
创建 Set 对象时,指定 Comparator 比较器接口,并实现 compare 方法

自然顺序

  • 如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口
  • 实现 Comparable 的类必须实现 compareTo(Object obj) 方法
  • 两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小
  • 对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值为 0

代码示例一:按照字符串Unicode编码值排序

@Test
    public void test1(){
        TreeSet<String> set = new TreeSet<>();
        set.add("zhangsan");  //String它实现了java.lang.Comparable接口
        set.add("lisi");
        set.add("wangwu");
        set.add("zhangsan");

        System.out.println("元素个数:" + set.size());
        for (String str : set) {
            System.out.println(str);
        }
    }

定制排序

  • 如果放到 TreeSet 中的元素的自然排序(Comparable)规则不符合当前排序需求时,或者元素的类型没有实现Comparable 接口
  • 那么在创建 TreeSe t时,可以单独指定一个 Comparator 的对象
  • 使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0

代码示例:学生类型未实现 Comparable 接口,单独指定 Comparator 比较器,按照学生的学号排序

public class Student{
    private int id;
    private String name;
    public Student(int id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    //......这里省略了name属性的get/set
    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + "]";
    }
}
@Test
    public void test3(){
        TreeSet<Student> set = new TreeSet(new Comparator<Student>(){

            @Override
            public int compare(Student o1, Student o2) {
                return o1.getId() - o2.getId();
            }

        });
        set.add(new Student(3,"张三"));
        set.add(new Student(1,"李四"));
        set.add(new Student(2,"王五"));
        set.add(new Student(3,"张三风"));

        System.out.println("元素个数:" + set.size());
        for (Student stu : set) {
            System.out.println(stu);
        }
    }