HashMap 中的 key 值类型

ops/2025/3/15 15:05:15/

在 Java 中,HashMapkey 一般建议使用 String 而不是自定义对象,主要有以下几个原因:

1. String 是不可变对象(Immutable)

  • String 在 Java 中是不可变的,一旦创建就不会改变其哈希值 (hashCode)。
  • HashMap 依赖 keyhashCode() 计算存储位置,如果 key 是可变对象,修改 key 后,它的 hashCode() 可能会改变,导致 HashMap 无法正确查找该 key,引发潜在问题(如数据丢失、无法查找等)。
  • 例如:
    java">Map<List<Integer>, String> map = new HashMap<>();
    List<Integer> key = new ArrayList<>();
    key.add(1);
    map.put(key, "value");key.add(2); // 修改 key
    System.out.println(map.get(key)); // 可能返回 null
    
    由于 ArrayListhashCode() 依赖于内容,key 变化后 hashCode 变化,导致 HashMap 无法找到原来的 value

2. StringhashCode() 计算高效且稳定

  • String 在 Java 中的 hashCode() 实现是经过高度优化的,并且被广泛使用,计算效率高。
  • StringhashCode() 计算方式如下:
    java">public int hashCode() {int h = 0;for (int i = 0; i < length(); i++) {h = 31 * h + charAt(i);}return h;
    }
    
  • 由于 Stringfinal 类,它的 hashCode() 计算逻辑不会被子类重写或修改,保证了哈希值的一致性。

3. String 具有良好的分布性,减少 Hash 冲突

  • HashMap 中,良好的 hashCode() 设计可以减少哈希冲突,提高查询效率。
  • StringhashCode() 计算方式能够较好地分布数据,避免大量 key 落在相同的桶(bucket)里。

4. String 适合作为标识符

  • String 直观易读,可以直接表示用户名、ID、类别等,便于代码理解。
  • 例如:
    java">Map<String, Integer> map = new HashMap<>();
    map.put("Alice", 25);
    map.put("Bob", 30);
    
    相比于自定义对象,String 更适合作为 key 来表示业务属性。

5. 避免 equals()hashCode() 方法实现错误

  • 使用自定义对象作为 key 时,需要正确实现 equals()hashCode() 方法,否则会导致 HashMap 行为异常,如无法正确查找 key 或发生哈希冲突。
  • 例如:
    java">class Person {String name;int age;
    }
    
    如果 Person 没有正确重写 equals()hashCode(),那么 HashMap 可能无法正确区分两个 Person 对象,即使它们的 nameage 相同。

6. String 具有内存优化(字符串常量池)

  • String 在 JVM 中有字符串常量池(String Pool),相同字符串可复用,减少内存占用。
  • 例如:
    java">String s1 = "hello";
    String s2 = "hello";
    System.out.println(s1 == s2); // true,共享同一个对象
    

总结

比较项使用 String 作为 key使用对象作为 key
不可变性不可变,哈希值稳定可能可变,影响 hashCode()
哈希分布性StringhashCode() 分布良好需要自定义 hashCode(),可能不均匀
易用性直观,易读易维护需要正确实现 equals()hashCode()
性能StringhashCode() 计算高效可能计算复杂,影响 HashMap 效率
内存优化享受 JVM String Pool 优势没有字符串池机制

因此,在 HashMap 中,推荐优先使用 String 作为 key,如果必须使用对象作为 key,需要确保:

  1. 该对象是不可变的;
  2. 正确重写 equals()hashCode() 方法。

HashMap 中,如果 key 是一个对象,并且该对象的某个属性发生变化,那么可能会导致无法通过原来的 key 找到对应的 value

为什么找不到原来的 value

  1. HashMap 依赖 keyhashCode() 计算存储位置
    • HashMap 通过 keyhashCode() 计算存储桶(bucket)的索引。
    • 如果 key 是对象,并且对象的某些属性影响了 hashCode() 计算,那么修改该属性会导致 hashCode() 变化,使得 HashMap 无法在原来的位置找到该 key
  2. HashMap 依赖 equals() 方法查找 key
    • HashMap 中,即使两个对象的 hashCode() 相同,最终还是要通过 equals() 方法确认它们是否相等。
    • 如果 equals() 方法依赖于某些可变属性,而这些属性发生了变化,那么即使 hashCode() 仍然相同,也可能会导致 equals() 失效,使得 HashMap 无法匹配原来的 key

示例代码

java">import java.util.HashMap;
import java.util.Objects;class Person {String name;int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic int hashCode() {return Objects.hash(name, age);}@Overridepublic boolean equals(Object obj) {if (this == obj) return true;if (obj == null || getClass() != obj.getClass()) return false;Person person = (Person) obj;return age == person.age && Objects.equals(name, person.name);}
}public class HashMapKeyTest {public static void main(String[] args) {HashMap<Person, String> map = new HashMap<>();Person p = new Person("Alice", 25);map.put(p, "Engineer");System.out.println("Before change: " + map.get(p)); // 正常获取 "Engineer"// 修改 key 对象的属性p.age = 30;System.out.println("After change: " + map.get(p)); // 可能返回 nullSystem.out.println("Contains key: " + map.containsKey(p)); // 可能返回 false}
}

分析

  1. Person 作为 key,它的 hashCode() 依赖于 nameage
  2. 存入 HashMap 后,p 计算的 hashCode() 确定了它在 HashMap 的存储位置。
  3. 修改 p.age 后,hashCode() 发生变化,导致 HashMap 在查找 p 时,计算出的 hashCode() 对应的存储桶位置可能已经改变。
  4. 结果:map.get(p) 可能返回 null,即无法找到原来的 value

如何解决?

方案 1:使用不可变对象作为 key

java">class Person {private final String name;private final int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic int hashCode() {return Objects.hash(name, age);}@Overridepublic boolean equals(Object obj) {if (this == obj) return true;if (obj == null || getClass() != obj.getClass()) return false;Person person = (Person) obj;return age == person.age && Objects.equals(name, person.name);}
}
  • 优点:保证 key 不变,避免 hashCode() 变化导致查找失败。
  • 适用场景:如果 key 需要稳定,建议使用 final 关键字让属性不可变,或者使用 String 作为 key

方案 2:使用 put() 重新存入修改后的 key

如果 key 发生变化,需要重新 put() 进去:

java">map.remove(p); // 先删除旧的 key
p.age = 30;    // 修改属性
map.put(p, "Engineer"); // 重新放入 HashMap
  • 适用场景:如果一定要修改 key,需要确保 HashMap 结构同步更新。

总结

方案影响适用场景
修改 key 属性hashCode() 变更,导致查找失败不推荐
使用不可变对象key 保持不变,避免问题推荐
使用 remove() 后重新 put()需要额外操作,保证 HashMap 一致性适用于可变对象

👉 最佳实践:建议 HashMapkey 选择 String 或不可变对象,避免因 hashCode() 变化导致查找失败!


http://www.ppmy.cn/ops/165966.html

相关文章

嵌入式学习L6网络编程D7TCP/ip协议

wireshark抓包工具 要下到linux里面去 windos这里要点wife然后就可以抓包 上面是过滤器 eth_ip头原理 就是IPV4 tcp三次握手

nerfstudio以及相关使用记录(长期更新)

NeRFStudio 是NeRF/3DGS研究和开发的集成平台。 提供了一个用户友好的界面和一系列工具&#xff0c;帮助研究人员和开发者更高效地构建、训练和评估 NeRF 模型。以前使用的时候用完就丢一边了&#xff0c;没有注意记录&#xff0c;现在有一个工程调用了nerfstudio&#xff0c;部…

DeepLabv3+改进9:在主干网络中添加SpatialGroupEnhance|通过为每个语义组中的每个空间位置生成注意力因子来调整每个子特征的重要性

🔥【DeepLabv3+改进专栏!探索语义分割新高度】 🌟 你是否在为图像分割的精度与效率发愁? 📢 本专栏重磅推出: ✅ 独家改进策略:融合注意力机制、轻量化设计与多尺度优化 ✅ 即插即用模块:ASPP+升级、解码器 PS:订阅专栏提供完整代码 论文简介 卷积神经网络(CNNs)…

软考计算机知识-流水线

计算机流水线类似工业生产过程的流水线&#xff0c;在同一时间&#xff0c;m个部件进行不同的操作&#xff0c;完成对不同对象的处理。 理解重叠&#xff1a;让不同的指令在时间上重叠地解释。在解释第k条指令的操作完成之前&#xff0c;就可以开始解释第k1条指令。 题1&#…

如何上传文件到github

如何上传文件到github **方法 1&#xff1a;使用 Git 命令行&#xff08;推荐&#xff09;****步骤 1&#xff1a;初始化 Git 仓库&#xff08;如果还没有&#xff09;****步骤 2&#xff1a;添加远程仓库****步骤 3&#xff1a;添加整个文件夹并提交****步骤 4&#xff1a;推送…

【 Manus平替开源项目】

文章目录 Manus平替开源项目1 OpenManus1.1 简介1.2 安装教程1.3 运行 2 OWL2.1 简介2.2 安装教程2.3 运行 3 OpenHands&#xff08;原OpenDevin&#xff09;3.1 简介3.2 安装教程和运行 Manus平替开源项目 1 OpenManus 1.1 简介 开发团队: MetaGPT 核心贡献者&#xff08;5…

【RTSP】客户端(三) 音频相关

ADTS头生成 根据给定的频率返回采样率索引 该索引信息主要是在ADTS头中进行使用 int GetSampleRateIndex(int freq){int i 0;int freq_arr[13] {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350};for(i 0; i < 13; i){if(fre…

【软件设计】23 种设计模式解析与实践指南

引言 设计模式&#xff08;Design Pattern&#xff09;是软件开发中反复出现的问题的解决方案&#xff0c;由 Erich Gamma 等四人组&#xff08;GoF&#xff09;在 1994 年系统化提出。 在软件开发领域&#xff0c;设计模式是解决常见软件设计问题的可复用方案。它们就像是建筑…