1.1 场景
想按创建时间降序列表展示订单信息,但最终返回给前端的数据和idList顺序不一致,乱序输出。
Debug发现有段代码,根据idList从数据库中查询出orderList,输出一个以订单编号为key,订单内容为value的Map,该Map输出内容是乱序的。
......
//根据订单idList查询订单列表
List orderList = orderDao.listOrderByIds(idList);
//输出以订单id为key的Map
Map orderMap = orders.stream().collect(Collectors.toMap(Order::getId, order -> order, (k1, k2) -> k1));
......
查看Collectors.toMap()源码发现其输出的Map是HashMap,而HashMap不是按顺序存的。
Collectors.toMap()方法源码:
public static
Collector> toMap(Function super T, ? extends K> keyMapper,
Function super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
public static
Collector> toMap(Function super T, ? extends K> keyMapper,
Function super T, ? extends U> valueMapper,
BinaryOperator mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
按照常规思维,往一个map里put一个已经存在的key,会把原有的key对应的value值覆盖。
然而第一个toMap()方法反其道而行之,它默认给抛异常…,
查看源码发现这个toMap方法默认使用了个throwingMerger,后边传进去的Map类型又写死是一个HashMap,所以最终走的是HashMap的merge方法。merge方法里面有这么一段代码:
if (old != null) {
V v;
if (old.value != null)
v = remappingFunction.apply(old.value, value);
else
v = value;
if (v != null) {
old.value = v;
afterNodeAccess(old);
}
else
removeNode(hash, key, null, false, true);
return v;
}
只看变量名就能知道这段代码啥意思了。。如果要put的key已存在,那么就调用传进来的方法。而throwingMerger的做法就是抛了个异常。。。
如果不想抛异常的话,自己传进去一个方法即可,上述代码可以改成:
Map map = list.stream().collect(Collectors.toMap(Person::getId, Person::getName,(oldValue, newValue) -> newValue));
在这里自己传进去一个方法之后,调用的就是第二个toMap方法了,第二个方法,通过传参来自定义对key发生冲突的处理了。
public static >
Collector toMap(Function super T, ? extends K> keyMapper,
Function super T, ? extends U> valueMapper,
BinaryOperator mergeFunction,
Supplier mapSupplier) {
BiConsumer accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}
如果想要输出有序,推荐使用LinkedHashMap。
LinkedHashMap除实现HashMap,还维护了一个双向链表。LinkedHashMap为每个Entry添加了前驱和后继,每次向linkedHashMap插入键值对,除了将其插入到哈希表的对应位置之外,还要将其插入到双向循环链表的尾部。
在循环遍历时,HashMap遍历的是tab[]数组,LinkedHashMap遍历的是双向链表,从head开始,即最初的链表顺序。
为保证输出有序,选择LinkedHashMap,具体修改方案如下:
LinkedHashMap orderMap = orders.stream().collect(Collectors.toMap(Order::getId, order -> order, (oldData, newData) -> newData,LinkedHashMap::new));
想要以id分组输出Map,结果输出是乱序的。
Map> map;
map = list.stream().collect(Collectors.groupingBy(OrderVO::getId));
其原因和Collectors.toMap()类似,查看源码可知Collectors.groupingBy()
输出为HashMap。
public static
Collector> groupingBy(Function super T, ? extends K> classifier,
Collector super T, A, D> downstream) {
return groupingBy(classifier, HashMap::new, downstream);
}
选择以LinkedHashMap输出。
map = list.stream().collect(Collectors.groupingBy(OrderVO::getId, LinkedHashMap::new, Collectors.toList()));