JavaSE | Set集合

99 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情

(四)Set集合

1.Set集合概述和特点

Set集合特点

  • 不包含重复元素的集合
  • 没有带索引的方法,所以不能使用普通for循环

Set集合练习

  • 存储字符串并遍历
import java.util.HashSet;
import java.util.Set;

public class SetDemo {
    public static void main(String[] args) {
        //创建集合对象
        Set<String> set = new HashSet<>();//HashSet对集合的迭代顺序不作任何保证

        //添加元素
        set.add("hello");
        set.add("java");
        set.add("world");
        //不包含重复元素
        set.add("world");//结果只有一个world

        //遍历集合
        for (String s : set) {
            System.out.println(s);
        }
    }
}

2.哈希值

哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值

Object类中有一个方法可以获取对象的哈希值

  • public int hashCode():返回对象的哈希码值

对象的哈希值特点

  • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
  • 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同

3.HashSet集合概述和特点

HashSet集合特点

  • 底层数据结构是哈希表
  • 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
  • 没有带索引的方法,所以不能使用普通for循环遍历
  • 由于是Set集合,所以是不包含重复元素的集合

HashSet集合练习

  • 存储字符串并遍历
import java.util.HashSet;
import java.util.Iterator;

public class HashSetDemo {
    public static void main(String[] args) {
        HashSet<String> sh = new HashSet<>();
        sh.add("hello");
        sh.add("world");
        sh.add("java");

        //迭代器遍历
        Iterator<String> it = sh.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }

        //增强for遍历
        for(String s : sh){
            System.out.println(s);
        }

    }
}

4.HashSet集合保证元素唯一性源码分析

HashSet集合添加一个元素过程:

image-20220101163525259

HashSet集合存储元素:

  • 要保证元素唯一性,需要重写hashCode()和equals()

5.案例(HashSet集合存储学生对象并遍历)

要求:学生对象的成员变量值相同,我们就认为是同一个对象

代码实现:

创建学生类(需要重写hashCode()和equals()方法来保证元素的唯一性)

import java.util.Objects;

public class Student {
    private String name;
    private int age;

    public Student() {

    }

    public Student(String name, int age) {
        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;
    }

    //重写重写hashCode()和equals()方法来保证元素的唯一性
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

创建测试类

import java.util.HashSet;
import java.util.Iterator;

public class HashSetDemo {
    public static void main(String[] args) {
        HashSet<Student> sh = new HashSet<>();
        Student s1 = new Student("学生1", 20);
        Student s2 = new Student("学生2", 21);
        Student s3 = new Student("学生3", 22);
        Student s4 = new Student("学生1",20);//与s1相同

        sh.add(s1);
        sh.add(s2);
        sh.add(s3);
        sh.add(s4);//结果并未显示s4,因为与s1位同一个元素,在学生类定义中重写了hashCode()和equals()方法确保了元素的唯一性

        //迭代器遍历集合
        Iterator<Student> it = sh.iterator();
        while (it.hasNext()) {
            Student s = it.next();
            System.out.println(s.getName() + ", " + s.getAge());
        }

        //增强for遍历集合
        for(Student s : sh) {
            System.out.println(s.getName() + ", " + s.getAge());
        }
    }
}

6.LinkedHashSet集合概述和特点

LinkedHashSet集合特点

  • 哈希表和链表实现的Set接口,具有可预测的迭代次序
  • 由链表保证元素有序,也就是 说元素的存储和取出顺序是一致的
  • 由哈希表保证元素唯一,也就是说没有重复的元素

LinkedHashSet集合练习

  • 存储字符串并遍历
import java.util.LinkedHashSet;

public class LinkedHashSetDemo {
    public static void main(String[] args) {
        //LinkedHashSet集合保证了元素的唯一性也保证了存储和取出的顺序的一致性
        LinkedHashSet<String> lh = new LinkedHashSet<>();
        lh.add("hello");
        lh.add("java");
        lh.add("world");

        lh.add("hello");//元素唯一性,所以结果不显示多余的hello字符串
        for (String s : lh) {
            System.out.println(s);
        }
    }
}

7.TreeSet集合概述和特点

TreeSet集合特点

  • 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法

    ​ TreeSet():根据其元素的自然排序进行排序

    ​ TreeSet(Comparetor comparator):根据指定的比较器进行排序

  • 没有带索引的方法,所以不能使用普通for循环遍历

  • 由于是Set集合,所以不包含重复元素

TreeSet集合练习

  • 存储整数并遍历
import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<Integer> ts = new TreeSet<>();
        //添加元素
        ts.add(2);
        ts.add(3);
        ts.add(1);

        //因为它是Set集合,所以不包含重复元素
        ts.add(2);

        for(Integer it: ts) {
            System.out.print(it);//结果为 1 2 3
        }
    }
}

8.自然排序Comparable的使用

  • 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
  • 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
  • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写

练习:

  • 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
  • 要求:按照年龄从小到大牌,年龄相同时,按照姓名的字母顺序排序

代码实现:

定义学生类:

public class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student() {
    }

    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;
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Student s) {
//        return -1;//降序
//        return 0;//元素重复
//        return 1;//升序

        int flag = this.age - s.age;    //this在前为升序,在后为降序
        int flag2 = flag == 0 ? this.name.compareTo(s.name) : flag;		//String类自带compareTo方法
        return flag2;
    }
}

创建测试类:

import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<>();
        Student s1 = new Student("学生1",22);
        Student s2 = new Student("学生2",23);
        Student s3 = new Student("学生3",33);

        Student s4 = new Student("学生4",33);

        Student s5 = new Student("学生4",33);
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);//s5与s4元素重复所以结果不显示

        for(Student s:ts) {
            System.out.println(s.getName()+", "+s.getAge());
        }
    }
}

9.比较器排序Comparator的使用

  • 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
  • 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
  • 重写方法时,一定要注意排序规则必须按照要求的主要和次要条件来写

练习:

  • 存储学生对象并遍历,创建TreeSet集合使用带参构造方法
  • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序

代码实现

定义一个标准的学生类:(此处省略)

定义测试类:

import java.util.Comparator;
import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                int flag = s1.getAge() - s2.getAge();
                int flag2 = flag == 0 ? s1.getName().compareTo(s2.getName()) : flag;
                return flag2;
            }
        });

        Student s1 = new Student("学生1",22);
        Student s2 = new Student("学生2",23);
        Student s3 = new Student("学生3",33);

        Student s4 = new Student("学生4",33);

        Student s5 = new Student("学生4",33);
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);//s5与s4元素重复所以结果不显示

        for(Student s:ts) {
            System.out.println(s.getName()+", "+s.getAge());
        }
    }
}

分析:

这里o1表示位于前面的对象,o2表示后面的对象

  • 返回-1(或负数),表示不需要交换01和02的位置,o1排在o2前面,asc
  • 返回1(或正数),表示需要交换01和02的位置,o1排在o2后面,desc

下面来用我们之前的结论解释为什么 return o2.a - o1.a 就是降序了:

首先o2是第二个元素,o1是第一个元素。无非就以下这些情况: ①: o2.a > o1.a: 那么此时返回正数,表示需要调整o1,o2的顺序,也就是需要把o2放到o1前面,这不就是降序了么。

②:o2.a < o1.a : 那么此时返回负数,表示不需要调整,也就是此时o1 比 o2大, 不还是降序么。


10.案例(不重复的随机数)

编写一个程序,获取10个1~20之间的随机数,要求随机数不能重复,并在控制台输出

import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

public class SetDemo {
    public static void main(String[] args) {
        //创建集合对象
        //Set<Integer> set = new HashSet<>();//HashSet集合是无序且元素唯一
        Set<Integer> set = new TreeSet<>();//TreeSet集合是有序且元素唯一

        //创建随机数对象
        Random r = new Random();

        //判断集合的长度是否小于10
        while (set.size() < 10) {
            int sum = r.nextInt(20) + 1;
            set.add(sum);
        }

        for(Integer i :set) {
            System.out.println(i);
        }
    }
}