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

HashMap 中的 key 值类型

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

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

  • String 在 Java 中是不可变的,一旦创建就不会改变其哈希值 (hashCode)。
  • HashMap 依赖 keyhashCode() 计算存储位置,如果 key 是可变对象,修改 key 后,它的 hashCode() 可能会改变,导致 HashMap 无法正确查找该 key,引发潜在问题(如数据丢失、无法查找等)。
  • 例如:
    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() 计算方式如下:
    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、类别等,便于代码理解。
  • 例如:
    Map<String, Integer> map = new HashMap<>();
    map.put("Alice", 25);
    map.put("Bob", 30);
    
    相比于自定义对象,String 更适合作为 key 来表示业务属性。

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

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

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

  • String 在 JVM 中有字符串常量池(String Pool),相同字符串可复用,减少内存占用。
  • 例如:
    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

示例代码

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;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public 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)); // 可能返回 null
        System.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

class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public 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() 进去:

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.kler.cn/a/587341.html

相关文章:

  • 重塑企业存储性能的终极引擎-NVMe集群
  • export、export default 和 module.exports 深度解析
  • 母婴商城系统Springboot设计与实现
  • LoRa数传、点对点通信、Mesh网络、ZigBee以及图传技术的区别和特点
  • H3C无线控制器二层注册典型配置举例
  • Android Dagger2 框架依赖图构建模块深度剖析(三)
  • 信息系统运行管理员教程6--信息系统安全
  • 《蓝耘容器全栈技术指南:企业级云原生与异构计算实战大全》
  • 搞定python之六----文件读写
  • 【IDEA插件开发】IntelliJ IDEA 插件开发指南
  • 《灵珠觉醒:从零到算法金仙的C++修炼》卷三·天劫试炼(47)乾坤图演路径 - 欧拉路径(Hierholzer 算法)
  • 使用DeepSeek和墨刀AI,写PRD文档、画原型图的思路、过程及方法
  • linux系统命令——权限
  • 【图论】并查集的学习和使用
  • 背诵--2
  • 如何用Deepseek制作流程图?
  • 2025年1月-3月Java面试题、笔记、简历模版汇总(需要自取)
  • 解锁C++:指针与数组、字符串的深度探秘
  • MySQL复习(检查本地MySQL是否安装、DataGrip数据库可视化工具使用、增删改查基础语法、唯一索引、SQL简单函数)
  • 【科研绘图系列】R语言绘制网络相关图(cor network plot)