跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

Java equals() 与 hashCode() 契约详解及容器类(List/Set/Map)核心整理

综述由AI生成Java 中 equals() 与 hashCode() 的共生契约关系,阐述违反契约在 HashMap 中导致的 Bug 原理。介绍了 HashMap 判断对象相等的“先哈希码后 equals”双重机制及其设计原因。同时系统整理了 List、Set、Map 三大接口及其常见实现类(如 ArrayList、HashSet、HashMap 等)的底层数据结构、线程安全性及适用场景,提供快速选择指南。

雾岛听风发布于 2026/3/30更新于 2026/5/2233 浏览
Java equals() 与 hashCode() 契约详解及容器类(List/Set/Map)核心整理

经典问题

  1. 为什么重写 equals() 一定要重写 hashcode?
  2. HashMap 是怎么判断两个对象是否相等的?
  3. HashMap 为什么要这么做?

一、为什么重写 equals() 一定要重写 hashCode()?

1. Java 官方的硬性契约(必须遵守)

java.lang.Object 类中明确规定了两个方法的共生关系:

  • 如果两个对象通过 equals() 比较相等,那么它们的 hashCode()必须返回相同的整数。
  • 如果两个对象通过 equals() 比较不相等,它们的 hashCode()可以相同也可以不同(但建议不同,以提高哈希表性能)。
2. 违反契约的后果(以 HashMap 为例)

如果你只重写了 equals() 而没重写 hashCode(),会导致哈希集合(HashMap、HashSet、HashTable 等)逻辑混乱:

  • 场景:你创建了两个对象 A 和 B,A.equals(B) == true,但 A.hashCode() != B.hashCode()。
  • 存入 HashMap:map.put(A, "value") 会根据 A 的哈希码找到一个位置存入。
  • 读取 HashMap:map.get(B) 会根据 B 的哈希码去找(可能找到另一个位置),结果找不到(认为是两个不同的 Key),尽管逻辑上它们是相等的。

二、HashMap 是怎么判断两个对象(Key)是否相等的?

HashMap 判断两个 Key 是否相等,遵循**'先哈希码,后 equals'**的双重检查机制:

  1. 第一步:比较 hashCode()
    1. 先调用 Key 对象的 hashCode() 方法,计算哈希值。
    2. 如果哈希值不同:直接判定两个对象不相等(连 equals 都不用比了,效率高)。
    3. 如果哈希值相同(哈希碰撞):进入第二步。
  2. 第二步:比较 equals()
    1. 调用 Key 对象的 equals() 方法进行内容比对。
    2. 如果 equals() 返回 true:判定两个对象相等(视为同一个 Key)。
    3. 如果 equals() 返回 false:判定两个对象不相等(虽然哈希冲突,但仍是不同 Key)。

三、HashMap 为什么要这么设计?

这是为了兼顾性能与准确性,本质是由 HashMap 底层的**'哈希表(数组 + 链表/红黑树)'**数据结构决定的:

1. 为什么要先比 hashCode()?(为了快)
  • HashMap 的核心优势是查询速度极快(接近 O(1))。
  • 它通过 hashCode 直接计算出 Key 在数组中的存储下标,从而快速定位。
  • 如果不先比 hashCode,而是每次都遍历所有元素调用 equals,那性能就退化成了链表(O(n)),失去了哈希表的意义。
2. 为什么还要比 equals()?(为了准)
  • 哈希碰撞是不可避免的:不同的对象可能算出相同的 hashCode(就像不同的人可能有相同的指纹概率)。
  • 因此,hashCode 相同只能说明它们应该放在数组的同一个'篮子'(链表/红黑树)里,但不能说明它们是同一个对象。
  • 必须通过 equals() 进行最终的内容确认,才能保证逻辑的绝对正确性。

总结

  • hashCode 决定了对象在哈希表中**'坐哪一排'**(快速定位)。
  • equals 决定了对象在这一排里**'是不是同一个人'**(精确匹配)。
  • 两者缺一不可,所以重写 equals 必须重写 hashCode。

List 接口(有序、可重复,按索引访问)

实现类底层数据结构核心特色
ArrayList动态数组1. 随机访问(索引)效率 O(1),增删需移动元素(效率 O(n));
2. 线程不安全,适合查询多、增删少的场景。
LinkedList双向链表(JDK1.7+ 取消循环)1. 增删只需修改节点指针(首尾操作 O(1)),随机访问需遍历(O(n));
2. 实现了 Deque 接口,可作为队列/栈使用;
3. 线程不安全,适合增删多、查询少的场景。
Vector动态数组1. 与 ArrayList 类似,但所有方法加 synchronized 修饰;
2. 线程安全但效率低,已很少使用。
Stack继承 Vector,动态数组1. 后进先出(LIFO);
2. 线程安全但效率低,推荐用 Deque(如 LinkedList)替代。

Set 接口(无序、不可重复,依赖 equals()+hashCode())

实现类底层数据结构核心特色
HashSet基于 HashMap(存储在 key 中)1. 哈希表(数组 + 链表 + 红黑树,JDK1.8+)实现;
2. 无序,添加/查询/删除效率平均 O(1);
3. 线程不安全,需重写 equals() 和 hashCode() 保证唯一性。
LinkedHashSet继承 HashSet,基于 LinkedHashMap1. 哈希表 + 双向链表实现;
2. 有序(按插入顺序或访问顺序);
3. 性能略低于 HashSet(需维护链表),线程不安全。
TreeSet基于 TreeMap(红黑树)1. 红黑树(自平衡二叉搜索树)实现;
2. 有序(自然排序或定制排序,需实现 Comparable 接口);
3. 增删改查效率 O(logn),线程不安全。

Map 接口(键值对存储,key 不可重复,value 可重复)

实现类底层数据结构核心特色
HashMap哈希表(数组 + 链表 + 红黑树)1. JDK1.8+:链表长度≥8 且数组长度≥64 时转红黑树;
2. 无序,允许1 个 null 键和多个 null 值;
3. 线程不安全,多线程需用 ConcurrentHashMap 替代。
LinkedHashMap继承 HashMap,哈希表 + 双向链表1. 维护插入顺序或访问顺序(accessOrder=true 时为 LRU 缓存);
2. 性能略低于 HashMap,线程不安全。
TreeMap红黑树1. 按 key自然排序或定制排序;
2. 不允许 null 键(但允许 null 值);
3. 增删改查效率 O(logn),线程不安全。
Hashtable哈希表(数组 + 链表,无红黑树)1. 所有方法加 synchronized 修饰;
2. 线程安全但效率低,不允许 null 键/值;
3. 已过时,推荐用 ConcurrentHashMap。
ConcurrentHashMap哈希表(数组 + 链表 + 红黑树)1. JDK1.8+:用 CAS+synchronized(只锁链表/红黑树头节点)替代分段锁;
2. 线程安全,效率远高于 Hashtable;
3. 不允许 null 键/值,适合高并发场景。

快速选择指南

  • List:选 ArrayList(查询多)或 LinkedList(增删多/队列)。
  • Set:选 HashSet(通用)、LinkedHashSet(需有序)、TreeSet(需排序)。
  • Map:选 HashMap(通用)、LinkedHashMap(需有序)、TreeMap(需排序)、ConcurrentHashMap(高并发)。

目录

  1. 经典问题
  2. 一、为什么重写 equals() 一定要重写 hashCode()?
  3. 1. Java 官方的硬性契约(必须遵守)
  4. 2. 违反契约的后果(以 HashMap 为例)
  5. 二、HashMap 是怎么判断两个对象(Key)是否相等的?
  6. 三、HashMap 为什么要这么设计?
  7. 1. 为什么要先比 hashCode()?(为了快)
  8. 2. 为什么还要比 equals()?(为了准)
  9. 总结
  10. List 接口(有序、可重复,按索引访问)
  11. Set 接口(无序、不可重复,依赖 equals()+hashCode())
  12. Map 接口(键值对存储,key 不可重复,value 可重复)
  13. 快速选择指南
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • C++ 容器适配器详解:Stack、Queue、Priority Queue 与 Deque
  • Ambari Web 3.0.0 本地启动与二次开发环境搭建
  • 预训练语言模型与 BERT 实战应用
  • Selenium+Python Web 自动化测试:元素定位、操作模拟与断言验证
  • 前缀和算法详解:从一维到二维的实战应用
  • 前端网页开发基础教程:HTML、CSS 与 JavaScript
  • JavaSE 多线程进阶知识
  • 基于 FPGA 的深度强化学习框架实现超音速闭环智能流动控制实验
  • C++ priority_queue 优先级队列详解与模拟实现
  • Claude Git 集成:代码协作与版本管理实战指南
  • GESP 2025 年 12 月 C++ 一级认证真题与解析:判断题 1-10
  • C++11 核心新特性详解:初始化、引用与移动语义
  • 工作中常用的几种设计模式实战
  • 双指针算法进阶:从三角形计数到四数之和
  • Virt-A-Mate 虚拟实境交互软件技术特性解析
  • 前端可视化界面开发:基于 Vue 构建 VibeThinker 交互平台
  • 基于优化理论的相位恢复算法
  • MoltBot 集成钉钉 Stream 流式接入配置指南
  • OpenClaw 爆火倒逼低代码 AI 变革:从工具赋能到生态重构
  • VSCode 搭建 Java+Maven 开发环境指南

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online