hashmap存取业务对象时,键必须重写hashcode()和equals(),否则put成功但get返回null;key不可变;null值需用containskey()区分;并发须用concurrenthashmap;泛型擦除要求序列化时严格类型匹配。

put() 和 get() 在 HashMap 中存取业务对象时,键必须可正确哈希
Java 里 HashMap 的 put() 和 get() 看似简单,但业务数据一上场就容易出错——最常见的是用自定义类当 key 却没重写 hashCode() 和 equals()。结果是 put() 成功了,get() 却返回 null。
原因很简单:HashMap 查找靠 hash 值定位桶,再用 equals() 判等。默认的 Object.hashCode() 返回内存地址,两个内容相同的对象 hash 值不同,自然找不到。
- 业务中常用订单号、用户 ID 字符串作 key,没问题;但若用
OrderRequest对象作 key,必须确保它重写了hashCode()和equals() - Lombok 用户可直接加
@Data(它包含@EqualsAndHashCode),但注意字段要选对——比如不含时间戳或随机 ID - 如果 key 是可变对象(如未设为 final 的字段),后续修改会导致 hash 值变化,
get()就再也匹配不到原来的位置
get() 返回 null 不一定代表没存过,得先排除 key 不匹配
get() 返回 null 是最让人迷惑的信号。它可能表示“没存过”,也可能表示“存过但 key 对不上”,甚至“存过且 key 对得上,但 value 本身就是 null”。
业务代码里常忽略第三种情况。比如缓存用户配置:map.put(userId, config),而 config 可能为 null(表示无配置)。这时 map.get(userId) == null 无法区分是缓存未命中,还是明确配置为空。
- 安全做法是用
map.containsKey(key)先确认 key 是否存在,再决定是否查 value - 更推荐初始化时避免存
null值:用Optional.empty()或专用空对象(如EmptyConfig.INSTANCE)代替null - 如果必须支持
nullvalue,别依赖== null判断,改用containsKey()+ 显式取值
并发场景下直接用 HashMap.put/get 会丢数据
多个线程同时调用 HashMap.put(),不加同步大概率触发扩容(resize),而扩容过程中的节点链表可能形成环形结构——接着 get() 就卡死在无限循环里(JDK 7);JDK 8 改用红黑树缓解,但依然不保证线程安全,仍会丢写、读到脏数据。
业务系统里常见后台任务和接口共用一个缓存 Map,一并发就出问题,错误现象包括:明明 put() 了却 get() 不到、日志显示存了两次但只生效一次。
- 读多写少:优先用
ConcurrentHashMap,它的put()和get()都是线程安全的,且分段锁/ CAS 比synchronized开销小 - 写极少(如启动时加载配置):可用
Collections.synchronizedMap(new HashMap()),但注意迭代时仍需手动同步 - 别用
Hashtable:方法全加synchronized,性能差,且已基本被ConcurrentHashMap取代
Map 接口方法签名里,泛型擦除不影响运行时行为,但影响编译期检查
声明 Map<string order></string> 后,你仍可能把 Integer put 进去——只要绕过泛型检查,比如反射调用或原始类型赋值。但正常编码下,泛型能拦住绝大多数类型误用。
真正容易被忽略的是:泛型只在编译期起作用,运行时所有 Map 都是裸类型。这意味着序列化/反序列化、跨服务传参时,如果对方没按约定泛型结构构造对象,get() 出来的 object 强转就会抛 ClassCastException。
- RPC 或 JSON 场景下,value 类型必须和服务端定义严格一致;比如后端返回
{"id":1,"name":"a"},但客户端期望Order,而实际反序列化成了LinkedHashMap,此时(Order) map.get("key")必崩 - 用 Jackson 时,别依赖
map.get("key")后强转,改用objectMapper.convertValue(map.get("key"), Order.class) - 泛型不能用于判断运行时类型,
if (map instanceof Map<string order>)</string>编译不过——类型信息已擦除
业务数据进 Map 不是塞进去就完事;key 的语义一致性、null 的含义边界、并发时机、泛型背后的运行时真相,每一步都卡着逻辑是否健壮。稍不注意,bug 就藏在 get() 返回的 null 里,等你线上查半天才反应过来——那根本不是没存,是 key 没对上。










