这节我们接着学习 Set 集合。

一、Set 集合

1.1 Set 概述

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

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

  •  Set是一种无序的集合
  •  Set是一种不带下标索引的集合
  •  Set是一种不能存放重复数据的集合
Set 接口继承体系:

Set 接口方法:

基础案例:

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

import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;public class SetTest {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()) {Object obj = it.next();System.out.println(obj);}}
}

运行结果:

hello1
hello4
hello5
hello2
hello3
------------------
hello1
hello4
hello5
hello2
hello3

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

1.2 HashSet

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

HashSet特点:
  • 无序性:HashSet中的元素的存储顺序与插入顺序无关

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

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

关键:存储自定义对象时,必须同时重写hashCode()equals(),否则无法保证唯一性(默认用地址值计算哈希,导致内容相同的对象被视为不同元素)。

  • 允许null元素:HashSet允许存储null元素,但只能存储一个null元素, HashSet中不允许重复元素
  • 高效性:HashSet的插入、删除和查找操作的时间复杂度都是O(1)

哈希表通过将元素的哈希值映射到数组的索引来实现快速的插入、删除和查找操作。

基础案例1:

实例化HashSet对象,往里面插入多个字符串,验证HashSet特点。

import java.util.HashSet;
import java.util.Set;public class HashSetTest {public static void main(String[] args) {//1.实例化HashSet对象Set<String> set = new HashSet<>();//2.添加元素set.add("hello");set.add("world");set.add("nihao");set.add("hello");set.add(null);set.add(null);System.out.println("size:"+set.size());//3.遍历for (String str : set) {System.out.println(str);}}
}

运行结果:

size:4
null
world
nihao
hello

根据结果可知,HashSet无序、唯一、null值可存在且只能存在一个。

元素插入过程:

当向HashSet中插入元素时,先获取元素的hashCode()值,再检查HashSet中是否存在相同哈希值的元素,如果元素哈希值唯一,则直接插入元素

如果存在相同哈希值的元素,会调用元素的equals()方法来进一步判断元素是否是相同。如果相同,则不会插入重复元素;如果不同,则插入元素

案例展示:

重写Student类的hashCode()方法:

class Student {private String id;       // 关键属性1(equals中比较)private String name;     // 关键属性2(equals中比较)private int age;         // 非关键属性(equals中不比较,hashCode中也不参与计算)// 构造方法public Student(String id, String name, int age) {this.id = id;this.name = name;this.age = age;}// 先重写equals()(必须与hashCode()基于相同属性)@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 (name == null) {if (other.name != null) {return false;}}else if (!name.equals(other.name)) {return false;}if (age != other.age) {return false;}return true;}// 重写hashCode()(基于id和name计算)@Overridepublic int hashCode() {int result = 17; // 初始化哈希种子(非零经验值)// 处理id(引用类型,可能为null)int idHash = (id == null) ? 0 : id.hashCode();result = 31 * result + idHash; // 31是质数,减少哈希冲突// 处理name(引用类型,可能为null)int nameHash = (name == null) ? 0 : name.hashCode();result = 31 * result + nameHash;return result; // 返回最终哈希值}
}

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

1.3 TreeSet

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

TreeSet特点:
  • 有序性:插入的元素会自动排序,每次插入、删除或查询操作后,TreeSet会自动调整元素的顺序,以保持有序性。
  • 唯一性:TreeSet中不允许重复的元素,即集合中的元素是唯一的。如果尝试插入一个已经存在的元素,该操作将被忽略。
  • 动态性:TreeSet是一个动态集合,可以根据需要随时添加、删除和修改元素。插入和删除操作的时间复杂度为O(log n),其中n是集合中的元素数量。
  • 高效性:由于TreeSet底层采用红黑树数据结构,它具有快速的查找和插入性能。对于有序集合的操作,TreeSet通常比HashSet更高效。
基础案例:

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

基础类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 Test_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: 
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 Test_Person.main(Test_Person.java:41)
问题分析:

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

解析:

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

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

如果一个类,实现了 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会认为这是重复的元素,只会保留一个

基础 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() 方法
  2. 该方法定义了元素之间的比较规则。在 compare() 方法中,我们可以根据元素的属性进行比较,并返回负整数、零或正整数,来表示元素之间的大小关系。
  3. 创建TreeSet对象时,将比较器对象作为参数传递给构造函数,这样,TreeSet 会根据比较器来进行排序。
compare方法说明:

         int result = compare(o1, o2);

  • result的值大于0,表示升序
  • result的值小于0,表示降序
  • result的值等于0,表示元素相等,不能插入

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

案例展示:

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

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;public class TestPerson {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());}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]

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

对比项自然排序(Comparable比较器排序(Comparator
实现位置元素类内部实现接口,重写 compareTo元素类外部创建比较器,重写 compare
排序规则数量一个类只能有一种自然排序规则一个类可以有多个比较器,实现不同规则
优先级若同时传比较器,自然排序会被覆盖传入比较器时,优先用比较器规则
适用场景元素有固定、单一排序规则(如 Integer 数值)需要临时、多样化排序(如有时按年龄,有时按姓名)
代码侵入性必须修改元素类代码(让其实现 Comparable无需修改元素类,外部单独定义规则

1.4 LinkedHashSet

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

案例展示:

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

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;public class Test_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 it = set.iterator();while (it.hasNext()) {System.out.println(it.next());}}
}

运行结果:

bbb
aaa
abc
bbc

1.5 Set 小结

下节我们学习Map集合。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/920533.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/920533.shtml
英文地址,请注明出处:http://en.pswp.cn/news/920533.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

金属结构疲劳寿命预测与健康监测技术—— 融合能量法、红外热像技术与深度学习的前沿实践

理论基础与核心方法 疲劳经典理论及其瓶颈 1.1.疲劳失效的微观与宏观机理&#xff1a; 裂纹萌生、扩展与断裂的物理过程。 1.2.传统方法的回顾与评析。 1.3.引出核心问题&#xff1a;是否存在一个更具物理意义、能统一描述疲劳全过程&#xff08;萌生与扩展&#xff09;且试验量…

【贪心算法】day4

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人的贪心算法学习以及LeetCode刷题记录&#xff0c;按专题划分每题主要记录&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代码&#xff1b;&#xff08;2&#xff09;优质解法 优质代码&#xff1b;&#xff…

AI 与脑机接口的交叉融合:当机器 “读懂” 大脑信号,医疗将迎来哪些变革?

一、引言&#xff08;一&#xff09;AI 与脑机接口技术的发展现状AI 的崛起与广泛应用&#xff1a;近年来&#xff0c;人工智能&#xff08;AI&#xff09;技术迅猛发展&#xff0c;已广泛渗透至各个领域。从图像识别、自然语言处理到智能决策系统&#xff0c;AI 展现出强大的数…

uniapp vue3 canvas实现手写签名

userSign.vue <template><view class"signature"><view class"btn-box" v-if"orientation abeam"><button click"clearClick">重签</button><button click"finish">完成签名</butt…

页面跳转html

实现流程结构搭建&#xff08;HTML&#xff09;创建侧边栏容器&#xff0c;通过列表或 div 元素定义导航项&#xff0c;每个项包含图标&#xff08;可使用字体图标库如 Font Awesome&#xff09;和文字&#xff0c;为后续点击交互预留事件触发点。样式设计&#xff08;CSS&…

Spring Boot自动装配机制的原理

文章目录一、自动装配的核心触发点&#xff1a;SpringBootApplication二、EnableAutoConfiguration的作用&#xff1a;导入自动配置类三、自动配置类的加载&#xff1a;SpringFactoriesLoader四、自动配置类的条件筛选&#xff1a;Conditional注解五、自动配置的完整流程六、自…

(未完结)阶段小总结(一)——大数据与Java

jdk8-21特性核心特征&#xff1a;&#xff08;8&#xff09;lambda&#xff0c;stream api&#xff0c;optional&#xff0c;方法引用&#xff0c;函数接口&#xff0c;默认方法&#xff0c;新时间Api&#xff0c;函数式接口&#xff0c;并行流&#xff0c;ComletableFuture。&…

嵌入式Linux驱动开发:设备树与平台设备驱动

嵌入式Linux驱动开发&#xff1a;设备树与平台设备驱动 引言 本笔记旨在详细记录嵌入式Linux驱动开发中设备树&#xff08;Device Tree&#xff09;和平台设备驱动&#xff08;Platform Driver&#xff09;的核心概念与实现。通过分析提供的代码与设备树文件&#xff0c;我们…

【完整源码+数据集+部署教程】骨折检测系统源码和数据集:改进yolo11-EfficientHead

背景意义 骨折作为一种常见的骨骼损伤&#xff0c;其诊断和治疗对患者的康复至关重要。传统的骨折检测方法主要依赖于医生的经验和影像学检查&#xff0c;如X光、CT等&#xff0c;这不仅耗时&#xff0c;而且容易受到主观因素的影响。随着计算机视觉和深度学习技术的迅猛发展&a…

面试记录7 c++软件开发工程师

开目 一面&#xff1a; 自我介绍你做的xxx应用是用c做的吗&#xff0c;是在window平台吗具体做的事情是什么你说的2D3D的结构是什么样的&#xff0c;怎样去做校验有没有二维到三维或者三维到二维的数据转换两个向量怎么去做校验做的什么优化有调用第三方库吗是用的什么工具&…

计算机网络:服务器处理多客户端(并发服务器)

一、服务器处理多客户端&#xff08;并发服务器&#xff09;&#xff08;一&#xff09;listen:监听客户端的连接请求&#xff0c;放入请求队列&#xff08;二&#xff09;accpet&#xff1a;请求队列中提取已连接的请求&#xff0c;返回连接好的fd&#xff08;循环accpet即可&…

Ansible自动化运维:原理以及安装教程

目录 Linux Ansible&#xff1a;作用与原理详解 一、Ansible 的核心作用 1. 配置管理&#xff08;Configuration Management&#xff09; 2. 应用部署&#xff08;Application Deployment&#xff09; 3. 任务编排&#xff08;Orchestration&#xff09; 4. 其他扩展作用 二、A…

[机器学习]基于K-means聚类算法的鸢尾花数据及分类

基于Kmeans&#xff0c;对鸢尾花数据集前两个特征进行聚类分析通过迭代优化&#xff0c;将150个样本划分到K个簇中。目标函数&#xff1a;最小化所有样本到其所属簇中心的距离平方和。算法步骤&#xff1a;随机初始化K个簇中心。将每个样本分配到最近的中心。计算均值确定每个簇…

Altium Designer 22使用笔记(10)---PCB铺铜相关操作

目录 01 | 简 述 02 | 环境描述 03 | 铺 铜 04 | 铺铜挖空 05 | 敷铜合并 06 | 敷铜的修改 07 | 总 结 01 | 简 述 在PCB设计阶段&#xff0c;除了布局、布线操作需要频繁进行调整外&#xff0c;铺铜操作的使用也非常频繁&#xff1b;因此本篇文章的主要内容为&#xff…

leetcode 338 比特位计数

一、题目描述二、解题思路我们可以借助位运算的思想来解决这个问题。通过kk&(k-1)来消除k中最右边为1的比特位&#xff0c;每次消除后进行count&#xff0c;当k为0时&#xff0c;表示所有的1消除完毕&#xff0c;此时的count即为所有1的个数。三、代码实现时间复杂度&#…

PHP的md5()函数分析

MD5&#xff08;Message-Digest Algorithm 5&#xff09;是一种广泛使用的哈希函数&#xff0c;由Ronald Rivest于1991年设计&#xff0c;属于密码散列算法家族。其核心功能是将任意长度的输入数据&#xff08;如字符串、文件等&#xff09;通过不可逆的数学运算转换为固定长度…

【面试场景题】怎么做业务领域划分

文章目录一、核心原则&#xff1a;以业务为中心&#xff0c;而非技术二、具体步骤&#xff1a;从业务理解到边界定义1. 深入理解业务&#xff1a;梳理业务全景2. 识别核心领域与支撑领域3. 划分“限界上下文”&#xff1a;定义领域边界4. 定义领域内的“聚合”&#xff1a;细化…

海量小文件问题综述和解决攻略(二)

1. 解决NameNode的内存问题 上面的内容提到过每个block的元数据都需要加载到NameNode的内存中&#xff0c;这导致一个Hadoop集群在NameNode中存储的对象是有上限的&#xff0c;并且对象太多会带来启动时间较长以及网络延迟的问题。常见的有两种解决方案&#xff0c;减少集群的…

《开发避坑指南:从异常中读懂系统的“求救信号”》

异常现象从不只是孤立的“故障”&#xff0c;而是系统发出的“健康预警”。太多团队困在“出现问题-临时修复-再次复发”的循环里&#xff0c;将精力消耗在表面问题的扑救上&#xff0c;却忽视了背后潜藏的架构缺陷、逻辑漏洞与环境适配盲区。真正成熟的开发思维&#xff0c;是…

数字孪生技术为UI前端赋能:实现产品性能的实时监测与预警

hello宝子们...我们是艾斯视觉擅长ui设计、前端开发、数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩!过去十年&#xff0c;前端技术栈翻天覆地&#xff1a;React/Vue/Angular、Webpack/Vite、Serve…