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

JavaSE丨集合框架入门(二):从 0 掌握 Set 集合

        这节我们接着学习 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.dtcms.com/a/356163.html

相关文章:

  • DeepSeek大模型风靡云平台,百度智能云、阿里云、腾讯云等多个平台宣布上线DeepSeek模型
  • HGDB全文检索/中文分词的使用
  • 美食推荐|美食推荐小程序|基于微信小程序的美食推荐系统设计与实现(源码+数据库+文档)
  • 【项目思维】通过编写一个贪吃蛇小程序,并移植到嵌入式设备上,解析编程思维的本质
  • mysql中的通用语法及分类
  • Ubuntu下配置并远程连接MySQL
  • 平安养老险深分开展“金融护航,安居鹏城”新市民金融服务宣传活动
  • Unity+URP+WebGL抗锯齿设置
  • MTK Linux DRM分析(二十二)- MTK mtk_drm_crtc.c(Part1)
  • PDF,HTML,md格式文件在线查看工具
  • CosyVoice win10启用记
  • 【mysql】SQL 中 IS 与 = 的区别:一个 NULL 值引发的思考
  • 编译esp32报错解决办法
  • 光谱相机多层镀膜技术如何提高透过率
  • [新启航]白光干涉仪在太阳能电池片栅线高度 3D 轮廓测量中的应用解析
  • 【C语言】递归
  • 屏随人动+视觉魔方+多样主题+智能留言,涂鸦Wukong AI 2.0助力打造爆款带屏云台相机
  • 从0开始学习Java+AI知识点总结-28.Linux部署
  • PDF 24 Tools, PDF编辑工具, PDF压缩, PDF转换, PDF删除, PDF加密, 添加水印
  • 创业灵感第一集
  • OpsManage:基于Django的企业级AWS云资源运维管理平台
  • 《零基础入门AI:YOLOv2算法解析》
  • Vue开发准备
  • 医疗AI时代的生物医学Go编程:高性能计算与精准医疗的案例分析(四)
  • OpenHarmony设备使用统计深度实战:从数据埋点到信息采集的全链路方案
  • 使用 Bright Data Web Scraper API + Python 高效抓取 Glassdoor 数据:从配置到结构化输出全流程实战
  • C++ 8.28
  • Dify学习
  • Python 正则表达式完全指南:从基础语法到实战案例
  • 深入理解文本向量嵌入(Vector Embeddings):原理、实践与应用场景