一、介绍
Set集合也是Collection集合的子类型,没有特有方法。Set比Collection定义更严谨,Set集合有如下要求
- 元素是不能重复的(不能存储两个对象,其equals方法比较返回true,只能存其中一个)
- 元素不能保证插入和取出顺序(无序)
- 元素是没有索引的
二、常用子类
常用子类有,HashSet、TreeSet、LinkedHashSet。
- HashSet:底层由HashMap,底层结构哈希表结构。去重、无索引、无序。哈希表结构的集合,操作效率会非常高。
- LinkedHashSet:底层结构链表加哈希表结构,具有哈希表结构的特点,也具有链表的特点。
- TreeSet:底层是由TreeMap,底层数据结构红黑树。去重,让存入的元素具有排序(升序排序)。
三、HashSet
3.1、概述
java.util.HashSet是Set接口的实现类,没有特有方法。底层是哈希表结构,具有去重特点。
例子
Set<Integer> set = new HashSet<>();
set.add(100);
set.add(300);
set.add(100);
set.add(200);
System.out.println(set); // [100, 200, 300]
3.2、去重原理分析
HashSet底层结构是哈希表结构,去重的实现借助了 两个方法:equals和hashCode方法。equals方法用来比较两个对象是否相等,hashCode方法用来生成哈希值的。
3.3、去重原理
一个元素存入到HashSet中,会先去比较是否有哈希值相同的元素(称为哈希冲突)。
如果哈希值不冲突,可以断定两个对象是肯定不一样的。
如果哈希值冲突了,进一步使用equals方法进行比较两个元素是否相同,若不相同可以存入。
3.4、去重注意点
HashSet集合,存储自定义类型元素时,要保证元素的唯一性时,需要在自定义类中重写toString()和equals()方法。
四、LinkedHashSet
LinkedHashSet的特点就是保障存取元素的顺序一致。
如果要考虑存取顺序一致的情况下,可以考虑使用LinkedHashSet。如果存取顺序一致不一致无所谓的情况下,就可以考虑使用HashSet。
例子
Set<Integer> set = new HashSet<>();
set.add(300);
set.add(100);
set.add(200);
System.out.println(set); // [100, 200, 300]
Set<Integer> set2 = new LinkedHashSet<>();
set2.add(300);
set2.add(100);
set2.add(200);
System.out.println(set2); // [300, 100, 200]
五、TreeSet
5.1、介绍
java.util.TreeSet集合是Set接口的一个实现类,底层依赖于TreeMap,是一种基于红黑树结构的实现,具有以下特点
- 元素去重
- 元素没有索引
- 元素有排序
TreeSet元素的排序规则,使用元素的自然排序规则,或者创建TreeSet时提供的Comparator比较器进行排序。
5.2、构造方法
// 根据其元素的自然排序进行排序
public TreeSet()
// 根据指定的比较器进行排序
public TreeSet(Comparator<E> comparatro)
5.3、TreeSet自然排序
一个类只要实现了接口Comparable就具备自然排序能力。比如如下类
- Short
- Double
- Integer
- String
例子
Set<Integer> set = new TreeSet<>();
set.add(300);
set.add(200);
set.add(100);
System.out.println(set); // [100, 200, 300]
Set<String> set2 = new TreeSet<>();
set2.add("abc");
set2.add("aaa");
set2.add("ccc");
set2.add("aba");
System.out.println(set2); // [aaa, aba, abc, ccc]
如果是自定义类型,可以在自定义类中实现类Comparable接口,重写compareTo方法更改自然排序的规则,如下
Student.java
public class Student implements Comparable{
private String name;
private int age;
// 满参、空参构造方法、get、set
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Object o) {
Student stu = (Student) o;
int result;
if(this.age == stu.age){
result = this.name.compareTo(stu.name);
}else{
result = this.age - stu.age;
}
}
}
// TreeSetTest.java
public class TreeSetTest {
public static void main(String[] args) {
Set<Student> set = new TreeSet<>();
set.add(new Student("张三", 24));
set.add(new Student("王五", 22));
set.add(new Student("李四", 22));
set.add(new Student("张三", 24));
set.add(new Student("王五", 23));
Iterator<Student> iter = set.iterator();
while(iter.hasNext()){
Student stu = iter.next();
System.out.println(stu);
// Student{name='李四', age=22}
// Student{name='王五', age=22}
// Student{name='王五', age=23}
// Student{name='张三', age=24}
}
}
}
5.4、TreeSet自定义排序
TreeSet可以实现自定义比较器排序,需要使用自定义比较器的构造方法,可以用来存储不具备自然排序能力的数据,也可以用来覆盖自然排序规则。
// 根据指定的比较器进行排序
public TreeSet(Comparator<E> comparator)
Comparator<E>是一个接口,我们需要去实现一个抽象方法compare,我们可以直接定义匿名内部类去实现该方法
public int compare(E o1, E o2){
return o1 - o2; // 升序
// 获取 return o2 - o1; // 降序
}
例子
// Student.java
public class Student {
private String name;
private int age;
// 空参、满参构造方法、get、set、
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
// TreeSetTest.java
public class TreeSetTest {
public static void main(String[] args) {
Set<Student> set = new TreeSet<>();
set.add(new Student("张三", 24)); // 报错
}
}
由于上述代码中Student类型没有实现Comparable接口,不具备自然排序的能力,所以就报错了,我们可以在Student中实现Comparable接口并重写compareTo方法自定义排序规则,解决此问题,如下
这样就不会有问题了,集合中顺序是以年龄的升序排序的,如果年龄一致的话,再次比较姓名进行排序。
也可以在不动Student类的情况下改进,在实例化TreeSet对象的时候,添加参数匿名Comparator对象,如下
public class TreeSetTest {
public static void main(String[] args) {
Set<Student> set = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
if(o1.getAge() == o2.getAge()){
return o1.getName().compareTo(o2.getName());
}else{
return o1.getAge() - o2.getAge();
}
}
});
set.add(new Student("张三", 24)); // 报错
set.add(new Student("王五", 22));
set.add(new Student("李四", 22));
set.add(new Student("张三", 24));
set.add(new Student("王五", 23));
Iterator<Student> iter = set.iterator();
while(iter.hasNext()){
Student stu = iter.next();
System.out.println(stu);
// Student{name='李四', age=22}
// Student{name='王五', age=22}
// Student{name='王五', age=23}
// Student{name='张三', age=24}
}
}
}
六、注意点
当存储的元素具备自然排序,又在TreeSet集合中指定了比较器排序,优先使用比较器排序。