前几期我们总结了有关于 Collection 的相关知识,这期来总结 关于Collection接口下的 存储有序元素可重复 List<E>接口 集合 和存储无序元素不可重复 Set<E> 接口 集合。
目录
4.6 Comparable 与 Comparator的区别
一、interface Set<E>
1.概念介绍
1.1 简述和特点
Set集合是一个存储元素不能重复的集合方式,因为存储数据的时候通过判断其元素的hashCode值,哈希值不同才进行存储
特点:
是Collection集合的子类
不包含重复的元素的集合
没有带索引的方法,所以不能用普通的for循环遍历
1.2 官方相关
不包含重复元素的集合。 更正式地,集合不包含一对元素
e1
和e2
,使得e1.equals(e2)
,并且最多一个空元素。 正如其名称所暗示的那样,这个接口模拟了数学集抽象。(就是元素不重复)Set接口除了继承自Collection接口的所有构造函数的合同以及add,equals和hashCode方法的合同外,还增加了其他规定,所有构造函数都必须创建一个不包含重复元素的集合
注意:如果可变对象用作设置元素,则必须非常小心。 如果对象的值以影响equals比较的方式更改,而对象是集合中的元素, 则不指定集合的行为。 这种禁止的一个特殊情况是,一个集合不允许将其本身作为一个元素。(一个集合不允许将其本身作为一个元素)
一些集合实现对它们可能包含的元素有限制。 例如,一些实现禁止空元素,有些实现对元素的类型有限制。 尝试添加不合格元素会引发未经检查的异常,通常为NullPointerException或ClassCastException 。 尝试查询不合格元素的存在可能会引发异常,或者可能只是返回false; 一些实现将展现出前者的行为,一些实现将展现出后者。 更一般来说,尝试对不符合条件的元素的操作,其完成不会导致不合格元素插入到集合中,可能会导致异常,或者可能会成功执行该选项。(异常类型)
2.set接口下的方法
boolean | add(E e) 如果指定的元素不存在,则将其指定的元素添加(可选操作)。 |
---|---|
boolean | addAll(Collection<? extends E> c) 将指定集合中的所有元素添加到此集合(如果尚未存在)(可选操作)。 |
void | clear() 从此集合中删除所有元素(可选操作)。 |
boolean | contains(Object o) 如果此集合包含指定的元素,则返回 true 。 |
boolean | containsAll(Collection<?> c) 返回 true 如果此集合包含所有指定集合的元素。 |
boolean | equals(Object o) 将指定的对象与此集合进行比较以实现相等。 |
int | hashCode() 返回此集合的哈希码值。 |
boolean | isEmpty() 如果此集合不包含元素,则返回 true 。 |
Iterator<E> | iterator() 返回此集合中元素的迭代器。 |
boolean | remove(Object o) 如果存在,则从该集合中删除指定的元素(可选操作)。 |
boolean | removeAll(Collection<?> c) 从此集合中删除指定集合中包含的所有元素(可选操作)。 |
boolean | retainAll(Collection<?> c) 仅保留该集合中包含在指定集合中的元素(可选操作)。 |
int | size() 返回此集合中的元素数(其基数)。 |
default Spliterator<E> | spliterator() 在此集合中的元素上创建一个 Spliterator 。 |
Object[] | toArray() 返回一个包含此集合中所有元素的数组。 |
<T> T[] | toArray(T[] a) 返回一个包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。 |
注:Set接口中没有额外定义新的方法,使用的都是 Collection 中声明的方法
3.HashSet
3.1 HashSet类介绍
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable此类实现Set接口,由哈希表(实际为HashMap实例)支持。 对集合的迭代次序不作任何保证; 特别是,它不能保证订单在一段时间内保持不变。 这个类允许null元素。
这个类提供了基本操作(add,remove,contains和size)固定的时间性能,假定哈希函数将分散的桶中正确的元素。 迭代此集合需要与HashSet实例的大小(元素数量)和后台HashMap实例(桶数)的“容量”的总和成比例的时间。 因此,如果迭代性能很重要,不要将初始容量设置得太高(或负载因子太低)是非常重要的。
请注意,此实现不同步。 如果多个线程并发访问哈希集,并且至少有一个线程修改该集合,那么它必须在外部进行同步。 这通常通过在自然地封装集合的一些对象上进行同步来实现。 如果没有这样的对象存在,那么该集合应该使用
Collections.synchronizedSet
方法“包装”。 这最好在创建时完成,以防止对该集合的意外不同步访问:Set s = Collections.synchronizedSet(new HashSet(...));该类iterator方法返回的迭代器是故障快速的 :如果集合在迭代器创建之后的任何时间被修改,除了通过迭代器自己的remove方法之外,迭代器会抛出一个
ConcurrentModificationException
。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。
3.2 HashSet特点
1、底层数据结构是哈希表
2、对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的先后顺序一致;
3、没有带索引的方法,所以不能用普通的for循环遍历, 但可以用加强for和 iterator 迭代器遍历
4、由于是Set集合,所以不包含重复元素
使用格式:
格式: Set<T> 对象名=new HashSet<T>(); //T是数据类型
HashSet 对象名=new HashSet<T>();
HashSet集合要保证元素唯一性:
HashSet是通过哈希值存储元素,使得元素的不重复性。
要保证唯一性,需要在引用类型中重写hashCode()和equals()方法;
3.3 HashSet构造方法
HashSet() 构造一个新的空集合; 背景HashMap 实例具有默认初始容量(16)和负载因子(0.75)。 |
---|
HashSet(Collection<? extends E> c) 构造一个包含指定集合中的元素的新集合。 |
HashSet(int initialCapacity) 构造一个新的空集合; 背景HashMap 实例具有指定的初始容量和默认负载因子(0.75)。 |
HashSet(int initialCapacity, float loadFactor) 构造一个新的空集合; 背景HashMap 实例具有指定的初始容量和指定的负载因子。 |
3.4 遍历HashSet集合
public class Demo01_HashSet_method {
public static void main(String[] args) {
Set<Integer> list = new HashSet<>();
list.add(100);
list.add(2);
list.add(22);
list.add(1);
list.add(10);
System.out.println(list);
// [1, 2, 100, 22, 10] 存储无序[***],不可重复
// 按照哈希值进行存储
int num = list.size();
System.out.println(num);
System.out.println(list.remove(33));//false
//for增强
for (Integer integer : list) {
System.out.println(integer);
}
//迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()){
iterator.next();
}
}
}
//输出结果:
[1, 2, 100, 22, 10]
5
false
1
2
100
22
10
3.5 HashSet 集合存储对象
class Person{
int id;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
//重写toString方法,使描述清晰
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
//重写equals方法 加上内容的对比
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return id == person.id && Objects.equals(name, person.name);
}
//重写hashCode方法,根据此类返回相应的哈希值
@Override
public int hashCode() {
return Objects.hash(id, name);
}
//这是保证在equalS为true 的基础上,两个数的哈希码值也一样,这样才能确保这是同一条数据
}
public class Demo02_HashSet_method_2 {
public static void main(String[] args) {
Set<Person> setPeople = new HashSet<>();
setPeople.add(new Person(1,"wangwu"));
setPeople.add(new Person(1,"wangwu"));
setPeople.add(new Person(1,"wangwu"));
System.out.println(setPeople);
}
}
//输出结果:[Person{id=1, name='wangwu'}]
3.6 简单案例
/*
创建Set接口的实现类,添加10个以上的元素,
通过foreach遍历此集合元素
*/
public class Test03 {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
//100以内的随机数选10个
set = randomNumSet(set,13,50);
for (Integer integer : set) {
System.out.println("元素:" + integer);
}
// Iterator<Integer> iterator = set.iterator();
// while (iterator.hasNext()){
// System.out.println("元素:" + iterator.next());
// }
}
//
public static Set<Integer> randomNumSet(Set<Integer> set,int num,int bound){
int count = 0;
while (count < num){
Random random = new Random();
int n = random.nextInt(bound);
if (!set.contains(n)){
set.add(n);
count++;
}
}
return set;
}
}
4.TreeSet
存储无序,元素按一定顺序排序,元素不重叠,底层是红黑树
4.1 TreeSet类介绍
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable需要注意的是由一组(无论是否提供了明确的比较器)保持的顺序必须与equals一致 ,如果它是要正确实现
Set
接口。 (参见Comparable
或Comparator
为一致的精确定义与equals)。这是因为该Set
接口在来定义equals
的操作,但一个TreeSet
例如使用其执行所有元件比较compareTo
(或compare
)方法,于是两个通过该方法认为相等的元素从集合的角度来看是相等的。 集合的行为是明确定义的,即使其排序与equals不一致; 它只是没有遵守Set
界面的总体合同。
4.2 TreeSet集合特点
1、元素有序:这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方法取决于构造方法。
TreeSet(): 根据其元素的自然排序进行排序;
TreeSet (Comparator comparator):根据指定的比较器进行排序
2、没有索引的方法:所以不能用普通的for循环遍历,可以用加强for或迭代器iterator
3、由于是Set集合,所以不包含重复元素
4.3 TreeSet类构造方法
TreeSet() 构造一个新的,空的树组,根据其元素的自然排序进行排序。 |
---|
TreeSet(Collection<? extends E> c) 构造一个包含指定集合中的元素的新树集,根据其元素的 自然排序进行排序 。 |
TreeSet(Comparator<? super E> comparator) 构造一个新的,空的树集,根据指定的比较器进行排序。 |
TreeSet(SortedSet<E> s) 构造一个包含相同元素的新树,并使用与指定排序集相同的顺序。 |
4.3 TreeSet类独有方法
常用的不多
E | ceiling(E e) 返回此集合中最小元素大于或等于给定元素,如果没有此元素,则返回 null 。 |
Comparator<? super E> | comparator() 返回用于对该集合中的元素进行排序的比较器,或null, 如果此集合使用其元素的natural ordering 。 |
Iterator<E> | descendingIterator() 以降序返回该集合中的元素的迭代器。 |
NavigableSet<E> | descendingSet() 返回此集合中包含的元素的反向排序视图。 |
E | first() 返回此集合中当前的第一个(最低)元素。 |
E | floor(E e) 返回此集合中最大的元素小于或等于给定元素,如果没有这样的元素,则返回 null 。 |
SortedSet<E> | headSet(E toElement) 返回此集合的部分的视图,其元素严格小于 toElement 。 |
NavigableSet<E> | headSet(E toElement, boolean inclusive) 返回该集合的部分的视图,其元素小于(或等于,如果 inclusive 为真) toElement 。 |
E | higher(E e) 返回严格大于给定元素的该集合中的最小元素,如果没有此元素则返回 null 。 |
E | last() 返回此集合中当前的最后(最高)元素。 |
E | lower(E e) 返回这个集合中最大的元素严格小于给定的元素,如果没有这样的元素,则返回 null 。 |
E | pollFirst() 检索并删除第一个(最低)元素,或返回 null 如果该集合为空。 |
E | pollLast() 检索并删除最后一个(最高)元素,如果此集合为空,则返回 null 。 |
boolean | remove(Object o) 如果存在,则从该集合中删除指定的元素。 |
int | size() 返回此集合中的元素数(其基数)。 |
Spliterator<E> | spliterator() 在此集合中的元素上创建late-binding和故障切换 Spliterator 。 |
NavigableSet<E> | subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) 返回该集合的部分的视图,其元素的范围从 fromElement 到 toElement 。 |
SortedSet<E> | subSet(E fromElement, E toElement) 返回此集合的部分的视图,其元素的范围从 fromElement (含)到 toElement ,排他。 |
SortedSet<E> | tailSet(E fromElement) 返回此组件的元素大于或等于 fromElement 的部分的视图。 |
NavigableSet<E> | tailSet(E fromElement, boolean inclusive) 返回此集合的部分的视图,其元素大于(或等于,如果 inclusive 为真) fromElement 。 |
4.4 TreeSet类的使用
4.4.1 创建对象格式:
格式: Set<T> 集合名=new TreeSet<T>(); // T表示数据类型
TreeSet<T> 集合名=new TreeSet<T>();
4.4.2 存一般数据:
public class Demo01 {
public static void main(String[] args) {
//TreeSet在存储数据的时候 会排序
Set<Integer> set = new TreeSet<>();
set.add(12);
set.add(15);
set.add(22);
set.add(35);
set.add(1);
System.out.println(set);//[1, 12, 15, 22, 35]
Set<String> set1 = new TreeSet<>();
set1.add("狗蛋");
set1.add("狗剩");
set1.add("a");
set1.add("cd");
System.out.println(set1);//按照字典顺序进行排序 (自然排序)
}
}
//输出结果:
[1, 12, 15, 22, 35]
[a, cd, 狗剩, 狗蛋]
4.4.3 TreeSet集合存储对象:
如果想要在TreeSet集合中添加对象的话,一定要去实现Comparable这个接口
自然排序Comparable的使用:
1、用TreeSet 集合存储定义对象,无参构造方法使用的是自然排序对元素进行排序的;
2、自然排序,就是让元素元素所属的类实现Comparable接口,重写compareTo(T o)方法
3、重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写。
compareTo(T o)抽象方法:
`int``compareTo(T o)`将此对象与指定的对象进行比较以进行排序。
//将此对象与指定的对象进行比较以进行排序。 返回一个负整数,零或正整数,因为该对象小于,等于或大于指定对象。
返回正数:往二叉树的右边添加
返回负数:往二叉树的左边添加
返回 0 : 说明重复,不添加
代码示例1:
class Student implements Comparable<Student>{ //!!!一定要实现Comparable这个接口
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@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);
}
//重写compareTo方法
@Override
public int compareTo(Student o) {
System.out.println("执行比较方法");//比较次数无规律
return this.age - o.age; // 比较的是 int age 属性
}
}
public class Demo02 {
public static void main(String[] args) {
Set<Student> set = new TreeSet<>();
set.add(new Student("wf",1));
//未实现接口前
//ClassCastException 类转换异常
//证明Student 转换不了Comparable
set.add(new Student("tf",2));
set.add(new Student("lf",3));
System.out.println(set);
}
}
代码示例2:
class Employee implements Comparable<Employee>{
String name;
int age;
int weight;
public Employee(String name, int age, int weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
'}';
}
@Override
public int compareTo(Employee o) {
//先按照年龄比,如果年龄相等,则比较体重
int num1 = this.age - o.age;
if (num1 == 0){
int num2 = this.weight - o.weight;
if (num2 == 0){
int num3 = this.name.hashCode()-o.name.hashCode();
return num3;
}else return num2;
}
return num1;
}
}
public class Demo02_TreeSet_2 {
public static void main(String[] args) {
Set<Employee> set = new TreeSet<>();
set.add(new Employee("茅台",48,78));
set.add(new Employee("蘑菇头",22,56));
set.add(new Employee("闰土",38,48));
set.add(new Employee("朱晓明",50,66));
set.add(new Employee("国魏",22,74));
set.add(new Employee("王宝钏",48,65));
set.add(new Employee("贾宝玉",48,56));
set.add(new Employee("林黛玉",50,70));
set.add(new Employee("黄天霸",16,65));
set.add(new Employee("流川枫",43,82));
set.add(new Employee("流川云",43,82));
//System.out.println(set);
for (Employee employee : set) {
System.out.println(employee);
}
}
}
//输出结果:
/*
Employee{name='黄天霸', age=16, weight=65}
Employee{name='蘑菇头', age=22, weight=56}
Employee{name='国魏', age=22, weight=74}
Employee{name='闰土', age=38, weight=48}
Employee{name='流川云', age=43, weight=82}
Employee{name='流川枫', age=43, weight=82}
Employee{name='贾宝玉', age=48, weight=56}
Employee{name='王宝钏', age=48, weight=65}
Employee{name='茅台', age=48, weight=78}
Employee{name='朱晓明', age=50, weight=66}
Employee{name='林黛玉', age=50, weight=70}
*/
4.5 使用比较器将数据存储到TreeSet中
class Student{
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
//实现比较器接口,重写compare方法
class MyComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age; // 按照学生年龄排序
}
}
public class Demo01 {
public static void main(String[] args) {
//如果想要使用比较器的写法,必须在newTreeSet 时侯加上比较器比较对象
Set<Student> students = new TreeSet<>(new MyComparator());
students.add(new Student("望天",12));
students.add(new Student("附件",46));
students.add(new Student("发放",54));
students.add(new Student("发我",22));
students.add(new Student("望热",12));
students.add(new Student("茅蹲",23));
System.out.println(students);
}
}
//输出结果:
[Student{name='望天', age=12}, Student{name='发我', age=22}, Student{name='茅蹲', age=23}, Student{name='附件', age=46}, Student{name='发放', age=54}]
4.6 Comparable 与 Comparator的区别
Comparable 和 Comparator 都是接口,均为比较器
Comparable相当于“内比较器”,而Comparator相当于“外比较器”
实现Comparable的类,该类就具有自身比较的功能;
Comparator的实现,是一个外部比较工具器
注意:
Comparable 和 Comparator 同时实现时,则以Comparator为主
Comparable compare To方法:
Comparator compare方法