当前位置: 首页 > news >正文

深入理解Java Set集合特性

1. Set概述

        java.util.Set 接口继承了 Collection 接口,是常用的一种集合类型。 相对于之前学习的List集合,Set集合特点如下:

        除了具有 Collection 集合的特点,还具有自己的一些特点:

                Set是一种无序的集合

                Set是一种不带下标索引的集合

                Set是一种不能存放重复数据的集合

    Set接口继承体系:

            

Set接口方法:

Set集合实现类:

重点学习的Set实现类:

        HashSet 底层借助哈希表实现

        TreeSet 底层借助二叉树实现

注意,TreeSet是Set接口的子接口SortedSet的实现类

基础案例:

        实例化一个Set集合,往里面添加元素并输出,注意观察集合特点(无序、不 重复)。

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;public class Test071_SetBasic {public static void main(String[] args) {// 1. 实例化Set集合,指向HashSet实现类对象Set<String> set = new HashSet<>();set.add("hello1");set.add("hello2");set.add("hello3");set.add("hello4");set.add("hello5"); // 重复元素,添加失败set.add("hello5"); // 重复元素,添加失败// 使用增强for循环遍历for (String obj : set) {System.out.println(obj);}System.out.println("-----------------");// 使用迭代器遍历Iterator<String> it = set.iterator();while (it.hasNext()) {String obj = it.next(); // 建议使用String而非Object,保持类型一致System.out.println(obj);}}
}hello1
hello4
hello5
hello2
hello3
-----------------
hello1
hello4
hello5
hello2
hello3

通过以上代码和运行结果,可以看出Set类型集合的特点:无序、不可重复

2. HashSet

        java.util.HashSet 是Set接口的实现类,它使用哈希表(Hash Table)作为其 底层数据结构来存储数据。

HashSet特点:

        无序性:HashSet中的元素的存储顺序与插入顺序无关 HashSet使用哈希表来存储数据,哈希表根据元素的哈希值来确定元素的存 储位置,而哈希值是根据元素的内容计算得到的,与插入顺序无关。

        唯一性:HashSet中不允许重复的元素,即每个元素都是唯一的

        允许null元素:HashSet允许存储null元素,但只能存储一个null元素, HashSet中不允许重复元素

        高效性:HashSet的插入、删除和查找操作的时间复杂度都是O(1) 哈希表通过将元素的哈希值映射到数组的索引来实现快速的插入、删除和查 找操作。

案例:

        实例化HashSet对象,往里面存入多个自定义类Student对象,观察结果。

自定义Student类:

public class Student {private String name;private int age;public Student() {}public Student(String name, int age) {super();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;}@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age + "]";}
}

测试类:

import java.util.HashSet;
import java.util.Set;
import com.briup.chap08.bean.Student;public class Test072_Student {public static void main(String[] args) {// 1. 实例化HashSet对象Set<Student> set = new HashSet<>();// 2. 往集合中添加元素set.add(new Student("zs", 20));set.add(new Student("zs", 20));set.add(new Student("zs", 20));set.add(new Student("zs", 20));System.out.println("size: " + set.size());// 3. 遍历for (Student stu : set) {System.out.println(stu);}}
}

从结果可知,HashSet认为4个数据成员一模一样的Student对象是不同的对象, 成功将它们添加到了集合中。这明显是不合理的,思考为什么?

元素插入过程:

  •         当向HashSet中插入元素时,先获取元素的hashCode()值,再检查HashSet中 是否存在相同哈希值的元素,如果元素哈希值唯一,则直接插入元素
  •         如果存在相同哈希值的元素,会调用元素的equals()方法来进一步判断元素 是否是相同。如果相同,则不会插入重复元素;如果不同,则插入

hashCode() 方法复习:

  •         hashCode() 方法是 Object类 中的一个方法,它返回一个 码值
  •         通常情况下, int 类型的哈希 hashCode() 方法会根据对象的属性值计算一个哈希码值(重 写自定义类中 hashCode方法 )
  •         如果两个对象的hashCode值不同,则两个对象的属性值肯定不同
  •         如果两个对象的hashCode值相同,则两个对象的属性值可能相同,也可能不 同

        在案例的基础上,重写Student类的hashCode和equals方法,再次运行程序观 察结果。

public class Student {private String name;private int age;// 省略,跟之前的一样...// 重写 hashCode 和 equals 方法,Alt+Shift+S 自动生成即可// 自动生成的 hashCode,根据类属性值计算得到哈希码@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + age;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Student other = (Student) obj;if (age != other.age)return false;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;}
}

重新运行程序,结果如下:

结论:如果要往HashSet集合存储自定义类对象,那么一定要重写自定义类中的 hashCode方法和equals方法。

3. TreeSet

        TreeSet是SortedSet(Set接口的子接口)的实现类,它基于红黑树(Red Black Tree)数据结构实现。

TreeSet特点:

        有序性:插入的元素会自动排序,每次插入、删除或查询操作后,TreeSet会 自动调整元素的顺序,以保持有序性。

        唯一性:TreeSet中不允许重复的元素,即集合中的元素是唯一的。如果尝试 插入一个已经存在的元素,该操作将被忽略。

        动态性:TreeSet是一个动态集合,可以根据需要随时添加、删除和修改元 素。插入和删除操作的时间复杂度为O(log n),其中n是集合中的元素数量。

        高效性:由于TreeSet底层采用红黑树数据结构,它具有快速的查找和插入性 能。对于有序集合的操作,TreeSet通常比HashSet更高效。

案例:

        准备一个TreeSet集合,放入多个自定义类Person对象,观察是否自动进行排序。

import java.util.Set;
import java.util.TreeSet;class Person {private String name;private int age;public Person() {}public Person(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;}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + "]";}
}public class Test073_Person {public static void main(String[] args) {// 1. 实例化 TreeSetSet<Person> set = new TreeSet<>();// 2. 添加元素set.add(new Person("zs", 21));set.add(new Person("ls", 20));set.add(new Person("tom", 19));// 3. 遍历集合for (Person person : set) {System.out.println(person);}}
}//程序运行,提示异常,具体如下:
Exception in thread "main" java.lang.ClassCastException: 
com.briup.chap08.test.Person cannot be cast to 
java.lang.Comparableat java.util.TreeMap.compare(TreeMap.java:1290)at java.util.TreeMap.put(TreeMap.java:538)at java.util.TreeSet.add(TreeSet.java:255)at 
com.briup.chap08.test.Test073_Person.main(Test073_Person.java:41)

问题分析:

        根据异常提示可知错误原因是: Person 无法转化成 ClassCastException异常 。 为什么会这样呢?

        TreeSet是一个有序集合,存储数据时,一定要指定元素的排序规则,有两种指 定的方式,具体如下:

TreeSet排序规则:

                自然排序(元素所属类型要实现 java.lang.Comparable 接口)

                比较器排序

        如果将Integer存入TreeSet不会报错,是因为Integer类已经实现自然排 序,而Person类既没有实现自然排序,也没有额外指定比较器排序规则。

1)TreeSet:自然排序

        如果一个类,实现了 java.lang.Comparable 接口,并重写了 compareTo方法,那么这个类的对象就是可以比较大小的。

 public interface Comparable<T> {public int compareTo(T o);}

compareTo方法说明:

        int result = this.属性.compareTo(o.属性);

  •                 result的值大于0,说明this比o大
  •                 result的值小于0,说明this比o小
  •                 result的值等于0,说明this和o相等

元素插入过程:

        当向TreeSet插入元素时,TreeSet会使用元素的 compareTo() 方法来比较元素 之间的大小关系。根据比较结果,TreeSet会将元素插入到合适的位置,以保持 有序性。如果两个元素相等( compareTo() 方法返回0),TreeSet会认为这是 重复的元素,只会保留一个。        

案例:

        使用自然排序(先按name升序,name相同则按age降序)解决上述案例问 题。

基础Person类:

// 定义Person类,实现自然排序
public class Person implements Comparable<Person> {private String name;private int age;public Person() {}public Person(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;}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + "]";}// 重写方法,指定比较规则:先按name升序,name相同则按age降序@Overridepublic int compareTo(Person o) {// 注意:字符串比较需要使用compareTo方法int r = name.compareTo(o.name);if (r == 0) {r = o.age - this.age; // 年龄降序}return r;}
}

测试类:

import java.util.Set;
import java.util.TreeSet;
import com.briup.chap08.bean.Person;// 自然排序测试
public class Test073_Comparable {public static void main(String[] args) {// 1. 实例化 TreeSetSet<Person> set = new TreeSet<>();// 2. 添加元素set.add(new Person("zs", 21));set.add(new Person("ww", 20));set.add(new Person("zs", 21));set.add(new Person("tom", 19));set.add(new Person("tom", 23));set.add(new Person("jack", 20));// 3. 遍历集合for (Person person : set) {System.out.println(person);}}
}//输出结果:
Person [name=jack, age=20]Person [name=tom, age=23]Person [name=tom, age=19]Person [name=ww, age=20]Person [name=zs, age=21]

补充内容:整形、浮点型、字符串自然排序规则

        对于整型、浮点型元素,它们会按照从小到大的顺序进行排序。对于字符串类 型的元素,它们会按照字典顺序进行排序。

注意事项:compareTo方法的返回结果,只关心正数、负数、零,不关心具体的值是多少

2)TreeSet:比较器排序

        思考:如果上述案例中Person类不是自定义类,而是第三方提供好的(不可以修 改源码),那么如何实现排序规则的指定?

我们可以可以使用比较器(Comparator)来自定义排序规则。

比较器排序步骤:

  1.         创建一个实现了Comparator接口的比较器类,并重写其中的 compare() 方法 ,该方法定义了元素之间的比较规则。在 compare() 方法中,我们可以根据 元素的属性进行比较,并返回负整数、零或正整数,来表示元素之间的大小 关系。
  2.         创建TreeSet对象时,将比较器对象作为参数传递给构造函数,这样,TreeSet 会根据比较器来进行排序。

java.util.Comparator 接口源码:

package java.util;@FunctionalInterface
public interface Comparator<T> {/*** Compares its two arguments for order. Returns a negative integer,* zero, or a positive integer as the first argument is less than, equal* to, or greater than the second.*/int compare(T o1, T o2);// 省略...
}

compare方法说明:

int result = compare(o1, o2);

                result的值大于0,表示升序

                result的值小于0,表示降序

                result的值等于0,表示元素相等,不能插入

注意,这里和自然排序的规则是一样的,只关心正数、负数、零,不关心具体的 值是多少

案例:

        使用比较器排序,对上述案例中Person进行排序,要求先按age升序,age相同 则按name降序。

基础类需要注释掉自然排序接口:

// 定义Person类,实现自然排序
public class Person /* implements Comparable<Person> */ {private String name;private int age;public Person() {}public Person(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;}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + "]";}// 重写方法,指定比较规则// @Override// public int compareTo(Person o) {//     // 先按name升序,name相同则按age降序//     int r = name.compareTo(o.name);//     if (r == 0) {//         r = o.age - this.age;//     }//     return r;// }
}

测试类:

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
import com.briup.chap08.bean.Person;// 比较器排序测试
public class Test073_Comparator {public static void main(String[] args) {// 1. 准备自定义比较器对象(匿名内部类形式)Comparator<Person> comp = new Comparator<Person>() {// 重写比较算法:先按 age 升序,age 相同则按 name 降序@Overridepublic int compare(Person o1, Person o2) {int r = o1.getAge() - o2.getAge();if (r == 0) {// 注意:字符串比较需要使用 compareTo 方法r = o2.getName().compareTo(o1.getName()); // name 降序}return r;}};// 2. 实例化 TreeSet,传入自定义比较器对象Set<Person> set = new TreeSet<>(comp);// 3. 添加元素set.add(new Person("zs", 21));set.add(new Person("ww", 20));set.add(new Person("zs", 21));set.add(new Person("tom", 19));set.add(new Person("tom", 23));set.add(new Person("jack", 20));// 4. 遍历集合for (Person person : set) {System.out.println(person);}}
}//输出结果:
Person [name=tom, age=19]Person [name=ww, age=20]Person [name=jack, age=20]Person [name=zs, age=21]Person [name=tom, age=23]

注意:如果同时使用自然排序和比较器排序,比较器排序将覆盖自然排序。

4. LinkedHashSet

        LinkedHashSet 是 HashSet 的一个子类,具有 HashSet 的高效性能和唯一性 特性,并且保持了元素的插入顺序,其底层基于哈希表和链表实现。

案例:

        实例化一个LinkedHashSet集合对象,存入多个String字符串,观察是否唯一及 顺序存储。

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;public class Test074_LinkedHashSet {public static void main(String[] args) {// 1. 实例化 LinkedHashSetSet<String> set = new LinkedHashSet<>();// 2. 添加元素set.add("bbb");set.add("aaa");set.add("abc");set.add("bbc");set.add("abc");// 3. 迭代器遍历Iterator<String> it = set.iterator();while (it.hasNext()) {System.out.println(it.next());}}
}//运行结果:
bbbaaaabcbbc

5. Set小结

http://www.dtcms.com/a/329664.html

相关文章:

  • windows下以all-in-one模式快速启动jaeger
  • Linux学习-UI技术
  • ROS2实用工具
  • Spring AI 的特性 及其 最佳实践
  • CompletableFuture介绍及使用方式
  • 天猫商品评论API:获取商品热门评价与最新评价
  • Jmeter TPS与QPS
  • Ant Design 的 `Image` 组件,通过 `preview.src` 加载本地图片文件
  • Dockerhub 代理设置
  • 破解测试数据困境:5招兼顾安全与真实性
  • Nature Communications 西湖大学姜汉卿教授:弹电磁驱动新范式--赋能昆虫级软体机器人的肌肉仿生策略
  • HTML第三次作业
  • Redis ubuntu下载Redis的C++客户端
  • Ubuntu 20.04 虚拟机安装完整教程:从 VMware 到 VMware Tools
  • 如何在 Ubuntu 24.04 LTS Noble Linux 上安装 FileZilla Server
  • Python【算法中心 03】Docker部署Django搭建的Python应用流程实例(Docker离线安装配置+Django项目Docker部署)
  • java中list的api详细使用
  • MySQL宝典
  • 【Golang】 Context.WithCancel 全面解析与实战指南
  • 使用内联汇编获取在线OJ平台CPU的信息
  • 玩转Docker | 使用Docker部署WordPress网站服务
  • 基本计算器 II
  • 回归分析预测原神深渊血量
  • 【金仓数据库产品体验官】_从实践看金仓数据库与 MySQL 的兼容性
  • Windows系统设置内外网同时访问(小白友好版)
  • Docker部署 Neo4j 及集成 APOC 插件:安装与配置完整指南(docker-compose)
  • 【Android】RecyclerView多布局展示案例
  • Kubernetes(K8S)中,kubectl describe node与kubectl top pod命令显示POD资源的核心区别
  • C语言:队列的实现和剖析
  • Spring Boot 整合 Thymeleaf 模板引擎:从零开始的完整指南