Set
上一篇文章大概地讲了一下List集合,本篇文章我们就来了解一下Set集合;
Set集合的概述和特点
依旧是那张结构图👇:
从结构图中可以看到Set集合继承于Collection接口,是一个不允许出现重复元素且没有顺序的集合,主要的实现类有HashSet和TreeSet两大实现类;
HasSet
特点
- 底层数据结构是哈希表
- 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以是不包含重复元素的集合
前面说到Set集合不允许出现重复的元素,在存储元素时,要保证元素唯一性需要重写hashCode()和equals()方法
如果元素的hashCode值相同,才会判断equals是否为true
如果hashCode的值不同,不会调用equals方法
注意❗️:对于判断元素是 否存在,以及删除等操作。依赖的方法是元素的hashCode和equals方法。
public static void main(String[] args) {
//创建学生对象
Student s1 = new Student("富贵", 23);
System.out.println(s1.hashCode());
System.out.println(s1.hashCode());
System.out.println("————————————");
//创建第二个学生对象
Student s2 = new Student("富贵", 23);
System.out.println(s2.hashCode());
}
运行结果:
356573597
356573597
————————————
1735600054
从👆程序中可以看到同一个对象多次调用hashCode()方法返回的哈希值是相同的,不同对象调用hashCode()方法的哈希值是不同的
哈希值 是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
HashSet集合的基本使用:
public static void main(String[] args) {
HashSet<String> hs = new HashSet<>();//创建集合对象
//添加元素
hs.add("富贵");
hs.add("招财");
hs.add("富贵");
//遍历集合
for (String s : hs) {
System.out.println(s);
}
}
运行结果:
富贵
招财
HashSet集合保证元素唯一性源码分析流程图
说了这么多不如看一下实际操作:
需求:
创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合
要求:学生对象的成员变量值相同,我们就认为是同一个对象
按照需求先新建一个学生类:
public class Student {
private String name;
private int age;
/*省略了构造方法和get、set方法………………*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
测试类:
public class HashSetDemo {
public static void main(String[] args) {
//创建HashSet集合对象
HashSet<Student> hs = new HashSet<Student>();
//创建学生对象
Student s1 = new Student("富贵", 28);
Student s2 = new Student("招财", 25);
Student s3 = new Student("进宝", 45);
Student s4 = new Student("进宝", 45);
//把学生添加到集合
hs.add(s1);
hs.add(s2);
hs.add(s3);
hs.add(s4);
//遍历集合(增强for)
for (Student s : hs) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
运行结果:
招财,25
富贵,28
进宝,45
LinkedHashSet
特点
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 由哈希表保证元素唯一,也就是说没有重复的元素
基本使用:
public class LinkedHashSetDemo {
public static void main(String[] args) {
//创建集合对象
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<String>();
//添加元素
linkedHashSet.add("富贵");
linkedHashSet.add("招财");
linkedHashSet.add("进宝");
linkedHashSet.add("富贵");
//遍历集合
for(String s : linkedHashSet) {
System.out.println(s);
}
}
}
运行结果:
富贵
招财
进宝
TreeSet
讲TreeSet集合之前先看先看一段代码:
public class TreeSetDemo {
public static void main(String[] args) {
//创建集合对象
TreeSet<Integer> ts = new TreeSet<Integer>();
//添加元素
ts.add(10);
ts.add(40);
ts.add(30);
ts.add(50);
ts.add(20);
ts.add(30);
//遍历集合
for (Integer i : ts) {
System.out.println(i);
}
}
}
运行结果:
10
20
30
40
50
结论:
- 元素有序,可以按照一定的规则进行排序,具体排序方式取决于构造方法
- TreeSet():根据其元素的自然排序进行排序
- TreeSet(Comparator comparator) :根据指定的比较器进行排序
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以不包含重复元素的集合
自然排序Comparable
还是先看一个案例,从案例中来分析
需求:
- 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
实现步骤
- 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
- 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
先新建一个学生类
public class Student implements Comparable<Student> {
private String name;
private int age;
/*省略了构造方法和get、set方法………………*/
@Override
public int compareTo(Student s) {
//按照年龄从小到大排序
int num = this.age - s.age;//主要条件
//从大到小排序
// int num = s.age - this.age;
//年龄相同时,按照姓名的字母顺序排序
int num2 = num==0?this.name.compareTo(s.name):num;//次要条件
return num2;
}
}
测试类
public class TreeSetDemo {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<Student>();
//创建学生对象
Student s1 = new Student("富贵", 29);
Student s2 = new Student("招财", 28);
Student s3 = new Student("进宝", 30);
Student s4 = new Student("钱多多", 33);
Student s5 = new Student("莱福",33);
Student s6 = new Student("莱福",33);
//把学生添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历集合
for (Student s : ts) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
运行结果:
招财,28
富贵,29
进宝,30
莱福,33
钱多多,33
结论
无参构造方法使用的是自然排序对元素进行排序的
自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(To)方法
在学生类实现Comparable接口
重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
比较器排序Comparator
按照步骤先看案例
需求
- 存储学生对象并遍历,创建TreeSet集合使用带参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
实现步骤
- 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
学生类
public class Student {
private String name;
private int age;
/*省略了构造方法和get、set方法………………*/
}
测试类
public class TreeSetDemo {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num = s1.getAge() - s2.getAge();//主要条件
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;//次要条件
return num2;
}
});
//创建学生对象
Student s1 = new Student("富贵", 29);
Student s2 = new Student("招财", 28);
Student s3 = new Student("进宝", 30);
Student s4 = new Student("钱多多", 33);
Student s5 = new Student("莱福",33);
Student s6 = new Student("莱福",33);
//把学生添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//增强for遍历集合
for (Student s : ts) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
运行结果:
招财,28
富贵,29
进宝,30
莱福,33
钱多多,33
结论
用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写comparetor(To1,To2)方法
在构造方法中传递一个Comparator比较器接口
重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写