Java 8 Stream 如何去除List<Map<String,Object>>中的重复Map元素
现有方案失效的核心原因
- 第一版编写的代码存在逻辑硬伤:流中每个元素的类型是
Map<String, Object>,本身不存在toLowerCase()方法,这段代码根本无法编译运行,属于将Map结构的处理逻辑和字符串的处理逻辑完全混淆。即使去掉这层无效的map转换,直接用HashSet<Map>存储元素,也会因为Map原生的equals判断规则不符合业务对“重复条目”的定义,无法实现预期去重效果。 - 第二版使用
distinct()方法无法生效的本质原因:Stream的distinct()底层完全依赖元素自身的equals()和hashCode()方法判断重复。虽然JDK自带的HashMap实现了内容比对的equals逻辑,但Spring JdbcTemplate返回的结果Map通常存在两个容易踩的坑:- 不同结果行的Map key大小写不统一(比如数据库原列返回大写key、自定义列别名返回小写key)
- 同语义的列值存在类型差异(比如同一数值列的结果,一行返回Long类型、另一行返回BigDecimal类型)
这种情况下,两个业务上完全等价的Map条目,会因为key大小写、值类型的细微差异,被equals方法判定为不相等,自然无法被distinct识别为重复元素。
可行的正确实现方案
根据不同的业务判定标准,提供两种可直接复用的实现:
方案一:按业务等价规则去重(即只要所有列的取值语义一致就算重复,不纠结key大小写、值的类型差异)
完全自定义重复判断规则,绕开Map原生equals的限制,用缓存存储每个条目的唯一业务标识做过滤:// 线程安全的去重标识缓存,适配并行流场景 Set<Object> dedupTagCache = ConcurrentHashMap.newKeySet(); List<Map<String, Object>> dedupList = apptlist.stream() .filter(itemMap -> { // 自定义生成当前Map的唯一业务标识 String itemUniqueTag = itemMap.entrySet().stream() // key统一转小写,消除大小写差异 .map(entry -> entry.getKey().toLowerCase() + "=" + // 值统一转字符串序列化,消除同值不同类型的差异 (entry.getValue() == null ? "" : entry.getValue().toString())) .sorted() // 对拼接片段排序,避免Map遍历顺序不同导致标识不一致 .collect(Collectors.joining("|")); // 标识成功加入缓存则保留元素,已存在则判定为重复直接剔除 return dedupTagCache.add(itemUniqueTag); }) .collect(Collectors.toList());方案二:按Map内容严格一致标准去重(即key、值完全匹配才算重复)
先对所有Map做规范化处理,统一key的格式消除原生equals的判断偏差,再调用Stream自带的distinct去重:List<Map<String, Object>> dedupList = apptlist.stream() .map(originalMap -> { // 统一把所有Map的key转小写,消除大小写差异 Map<String, Object> normalizedMap = new HashMap<>(); originalMap.forEach((key, value) -> normalizedMap.put(key.toLowerCase(), value)); return normalizedMap; }) // 规范化后Map的equals判断符合预期,distinct即可正常生效 .distinct() .collect(Collectors.toList());
实现注意事项
- 如果Map中存储的是自定义复杂对象,需要根据实际业务调整唯一标识的生成规则,避免直接调用对象
toString()导致同内容对象生成不同标识的问题。 - 若使用并行流做处理,缓存容器必须使用线程安全的实现,不要直接用普通HashSet/HashMap,否则会出现并发安全问题。
内容的提问来源于stack exchange,提问作者sandy
相关产品推荐
相关产品推荐

