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

Java中的Set集合如何保证元素唯一性

Set.add() 拒绝重复元素依赖 HashMap 的 key 去重机制:先用 hashCode() 定位桶,再用 equals() 精确判断,必须同时重写 hashCode() 和 equals() 并保持合同一致,否则会导致重复添加或搜索失败。

Set.add() 为什么能拒绝重复元素?

靠的是HashSet底层用HashMap存储时对key去重逻辑——不是通过经验比较,而是通过hashCode()定位桶的位置,然后使用equals()准确判断是否存在。

常见错误现象:new HashSet<Person>().add(new Person("Alice", 25));两次添加完全相同的对象,结果完全相同 size 变成 2.这是因为没有重写hashCode()equals(),两个新对象hashCode()不同的(默认为内存地址)根本不会到达equals()比较那步。

  • 必须同时重写hashCode()equals(),只改一个等于白改
  • equals()返回true两个对象,hashCode()必须返回相同的值(这是合同)
  • 相反,它不成立:hashCode()相同,equals()不一定为true(哈希冲突正常)

重写 equals() 时漏掉 null 或者类型检查会发生什么

典型翻车代码:public boolean equals(Object o) { return this.name.equals(o.name); }—— 一旦传入null或非Person类型,直接抛NullPointerExceptionClassCastException,导致Set.contains()remove()全挂。

正确的写作方法必须包括三段检查:

public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return Objects.equals(name, person.name) && age == person.age; }
  • 第一行处理自反性(自己等于自己)
  • 第二行拦截null与类型不匹配,避免运行异常
  • 第三行只做字段比较,并且使用Objects.equals()安全处理null字符串

hashCode() 使用错误的字段或计算方法 Set 失效

比如Person类里只拿namehashCode(),但equals()却比较name + age,会违反合同:两个age不同但name对象相同,hashCode()相同,equals()却返回false—— 它们会被塞进同一个桶里,但是Set误判为“可能相等”,实际不等,造成逻辑混乱甚至内存泄漏。

  • hashCode()使用的字段必须和谐equals()判断逻辑完全一致
  • 推荐用Objects.hash(name, age)不要手写乘加(容易出错,难以维护)
  • 如果字段可以改变(如果将来会修改)name),放进HashSet改变后,会导致对象“丢失”——它原本在桶里 A,改完 hash 变成桶 B,但没人通知 Set 去挪位置

TreeSet 场景下 equals/hashCode 根本不生效

假如你用的是TreeSet,以上所有关于hashCode()equals()所有的讨论都跑偏了——TreeSet不依赖哈希,它依赖ComparableComparator排名,唯一性由compareTo()compare()返回值决定:返回 0 就算重复。

常见错误:实现了类别Comparable,但compareTo()逻辑和equals()不一致。例如compareTo()只比name,而equals()还要看id。这时TreeSet会把name相同但id不同的两个对象被视为重复,直接吞下第二个对象。

  • TreeSet定义和“唯一性”HashSet完全不同,不能混合预期
  • 如果必须用TreeSet并且需要严格独特的业务,确保compareTo()equals()语义一致,或简单使用TreeSet+ 自定义Comparator控制逻辑
  • 没有实现Comparable且没传Comparator,往TreeSet里 add 就抛ClassCastException

事情说清楚了就结束了。事实上,最常被忽视的是:把对象放进去HashSet然后改变影响hashCode()字段比错误的写作方法更隐蔽、更难调查。

http://www.jsqmd.com/news/507924/

相关文章:

  • Oracle/MySQL/PostgreSQL字段类型对比详解 - a
  • 卷积神经网络在气象图像分析中的辅助应用:与伏羲模型协同工作
  • C语言混淆与控制流平坦化进阶方案(军工所内部白皮书节选)
  • 【研报247】2026年固态电池产业解析:宽温域优势的车规级Pack+航天应用双主线
  • GLM-4.7-Flash小白友好教程:无需GPU,云端一键体验最强30B模型
  • Mac升级Big Sur/Monterey后管理员权限丢失?深入解析.AppleSetupDone文件位置与恢复方案
  • Arch Linux更新报错:community.db缺失的根源分析与修复指南
  • Elsevier Tracker:智能审稿状态监控系统助力学术研究者提升投稿管理效率
  • SecGPT-14B实战教程:Python脚本批量调用API,构建企业级安全FAQ智能检索
  • 5分钟搞定!用Coze和Dify搭建你的第一个AI聊天机器人(零代码实战)
  • Linux新手必看:10个最常用指令+5个隐藏技巧(附真实场景案例)
  • 华南理工数字信号处理期末考突击指南:2023年最新复习卷1解析与高频考点
  • UniApp开发实战:5分钟搞定H5跨域代理配置(附manifest.json示例)
  • 避坑指南:OpenMMLab环境配置中的版本兼容性玄学
  • 超越VLC?实测3款冷门但超强的Linux播放器(附4K/HDR测试结果)
  • C语言直驱超导量子处理器:从PCIe原子写入到微秒级脉冲调度的7步工业级实现路径
  • 从网工软考真题看码元速率:2024最新解题技巧与常见陷阱
  • Jenkins实时日志显示背后的WebSocket技术揭秘(附源码解析)
  • 联邦学习与边缘AI的结合:AI原生应用的分布式智能
  • 鸿蒙UI开发实战:如何用wrapBuilder封装Builder函数(附完整代码示例)
  • LoRaWAN网关与ChirpStack服务器的高效集成实践
  • BeanFactory vs ApplicationContext:Spring新手必知的5个核心区别
  • AI技术平民化时代,程序员的“硬核”竞争力是什么?
  • Qwen3.5-9B入门指南:视觉-语言统一建模初学者理解路径与示例
  • 坐标转换(相互对应+边界)
  • 大模型 RAG 实战:从零手把手构建知识库问答系统,建议收藏
  • 保姆级避坑指南:用STM32+MPU9250给ROS小车做IMU与编码器数据融合(附完整代码)
  • 人像摄影实战:佳能6D搭配小痰盂镜头的多场景风格参数详解
  • 如何系统性地减少大模型“幻觉”:从提示词工程到架构设计
  • FreeRadius+OpenLDAP网络认证避坑指南:常见配置错误与解决方案