Java——Set
一、介绍
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集合中指定了比较器排序,优先使用比较器排序。