hashmap中修改key对象属性导致get()找不到值,是因为hashcode()变化使查找定位到错误bucket,而原entry仍留在旧bucket中;根本原因是用可变对象作key违反哈希表契约。

为什么改了对象属性后 get() 就找不到值了
因为 HashMap 查找键依赖两步:先用 hashCode() 定位 bucket,再用 equals() 在链表/红黑树里逐个比对。如果对象作为 key 后被修改,它的 hashCode() 值很可能变了——但 entry 还留在原来的 bucket 里,get() 会去错的地方找,自然返回 null。
- 常见错误现象:
map.put(user, "admin"); user.setName("newName"); map.get(user)返回null,哪怕user还是同一个实例 - 根本原因不是“没重写
equals()”,而是“改了影响hashCode()的字段” - 哪怕你重写了
hashCode()和equals(),只要对象可变,就无法保证哈希值稳定——这违反了哈希表的基本契约
哪些类天然适合作为 HashMap 的 Key
Java 标准库中能直接当 key 用的,基本都满足「不可变 + 正确实现 hashCode()/equals()」。比如 String、Integer、LocalDate、UUID。
在整本书中我们所涉及许多的Flex框架源码,但为了简洁,我们不总是显示所指的代码。当你阅读这本书时,要求你打开Flex Builder,或能够访问Flex3框架的源码,跟随着我们所讨论源码是怎么工作及为什么这样做。 如果你跟着阅读源码,请注意,我们经常跳过功能或者具体的代码,以便我们可以对应当前的主题。这样能防止我们远离当前的主题,主要是讲解代码的微妙之处。这并不是说那些代码的作用不重要,而是那些代码处理特别的案例,防止潜在的错误或在生命周期的后面来处理,只是我们当前没有讨论它。有需要的朋友可以下载看看
-
String是典型:内部char[]被final修饰,所有“修改”方法(如substring())都返回新对象 -
Integer等包装类也是final类,字段私有且无 setter - 自定义类想当 key?必须确保:所有参与
hashCode()计算的字段都是final,构造时初始化,之后绝不暴露修改入口
误用可变对象作 Key 的真实后果
不只是 get() 失败——更隐蔽的问题是数据“幽灵化”:entry 还在 map 里,但谁都访问不到,还占着内存,GC 也收不走(除非 map 被回收)。
- 调试困难:
map.size()显示有 5 个 entry,map.keySet().forEach(System.out::println)却只打印出 3 个能正常get()的 - 多线程下更危险:一个线程刚 put,另一个线程立刻改了 key 字段,随后任何线程调用
get()都可能失败,且无异常抛出 - 序列化/反序列化后,如果对象字段被重设(比如 JSON 反序列化绕过构造函数),
hashCode()和原始不一致,key 彻底失效
如果非得用可变对象,有没有折中方案
没有安全的折中。所谓“先 remove() 再 put()”只是把问题显性化,但业务逻辑上很难保证每次修改都同步操作 map;而用 TreeMap 改用比较器?它不要求不可变,但一旦比较依据变化,同样导致查找失败或结构损坏。
- 真正可行的做法只有两个:换不可变 key(推荐),或彻底不用对象做 key(改用 ID 字符串、数字等原始类型)
- 若必须封装,可定义一个只读视图类(
UserKey),构造时拷贝关键字段,不提供任何 setter,并声明为final - 千万别在 key 对象里留
public字段或setter——哪怕你“保证不用”,IDE 或反射都能绕过
hashCode() 在生命周期内变动,整个 map 的行为就脱离控制了——而这种 bug 往往只在特定数据组合或并发场景下才浮现。









