java无原生multimap,需用map手动管理或guava的multimap;注意空列表清理、线程安全及kotlin中getorput正确用法。

Java 里没有原生 MultiMap,得自己用 Map<k list>></k> 搭
Java 标准库的 HashMap、TreeMap 都不支持重复键——这是设计使然,不是 bug。想存多个值到同一个键下,最直接靠谱的做法就是把 value 类型定为 List(或 ArrayList、LinkedList),手动维护列表生命周期。
常见错误是每次 put 都直接覆盖:map.put("k1", Arrays.asList("v1")),下次再 put("k1", ...) 就丢了前一个值。
- 初始化时用
computeIfAbsent安全获取或新建列表:map.computeIfAbsent("k1", k -> new ArrayList()).add("v1") - 避免每次 new 列表:不用
getOrDefault(key, new ArrayList()),它会在 key 存在时也 new 一次空列表 - 如果频繁增删、且对顺序无要求,
LinkedHashSet作 value 可去重,但别用HashSet——迭代顺序不可靠
Guava 的 Multimap 看起来省事,但要注意默认实现不线程安全
Guava 提供了 ArrayListMultimap、HashMultimap 这类开箱即用的 Multimap,写法简洁:multimap.put("k1", "v1")、multimap.get("k1") 直接返回 Collection。
但它的所有标准实现(包括 ArrayListMultimap)都不是线程安全的。多线程并发 put 同一键,可能抛 ConcurrentModificationException 或静默丢数据。
- 需要并发安全?别套
Collections.synchronizedMap,得用MultiMaps.synchronizedMultimap() -
get(key)返回的是视图(view),不是新集合——修改它会直接影响底层 multimap - 序列化支持有限:
ArrayListMultimap可序列化,但TreeMultimap要求 key/value 都可比较,否则运行时报ClassCastException
用 Map<k list>></k> 时,remove 和 clear 容易漏掉清理空列表
手动管理 List 值的最大坑是“删值不删键”:调用 list.remove("v1") 后,如果列表变空,map.get("k1") 仍返回空 List,后续 size() 判断或遍历都得额外判空。
这不是语义错误,但会让逻辑变臃肿,尤其在做统计、过滤或序列化时容易出错。
- 删完记得检查:
if (list.isEmpty()) map.remove("k1") - 批量删值建议封装方法,比如
removeAllValues(map, key, values),内部统一处理空列表清理 - 不要依赖
map.values().stream().filter(List::isEmpty).count()来判断“是否还有有效映射”——空列表本身就算一个映射项
Kotlin 用户别用 mutableMapOf() 直接套 mutableListOf() 当 value
Kotlin 写 val map = mutableMapOf<string mutablelist>>()</string> 看似没问题,但 map["k1"]?.add("v1") 在 key 不存在时返回 null,不会自动创建列表——这和 Java 的 computeIfAbsent 行为不一致,新手常卡在这儿。
更隐蔽的问题是:Kotlin 的 getOrPut 是安全的,但若传入 { mutableListOf() },每次都会 new 新列表,哪怕 key 已存在(因为 lambda 是惰性求值,但没做存在性短路)。
- 正确写法:
map.getOrPut("k1") { mutableListOf() }.add("v1")——getOrPut保证只执行一次 lambda - 如果要用不可变语义,
map = map + ("k1" to (map["k1"] ?: emptyList()) + "v1")性能差,别在循环里用 - Android 开发注意:旧版 Kotlin 标准库中
getOrPut在某些版本有竞态 bug,建议升级到 1.8+ 或加同步块










