Collectors.toMap()方法——Java8

1. Collectors.toMap() 输出乱序

1.1 场景
想按创建时间降序列表展示订单信息,但最终返回给前端的数据和idList顺序不一致,乱序输出。
Debug发现有段代码,根据idList从数据库中查询出orderList,输出一个以订单编号为key,订单内容为value的Map,该Map输出内容是乱序的。

......
//根据订单idList查询订单列表
List<Order> orderList = orderDao.listOrderByIds(idList);  
//输出以订单id为key的Map
Map<Long, Order> orderMap = orders.stream().collect(Collectors.toMap(Order::getId, order -> order, (k1, k2) -> k1));
......

查看Collectors.toMap()源码发现其输出的Map是HashMap,而HashMap不是按顺序存的。

Collectors.toMap()方法源码:

2,Collectors.toMap()有三个重载方法:

第一个默认生成HashMap:

public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper) {
        return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
    }

第二个默认生成也是一个HashMap:

public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction) {
        return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
    }

第二个和第一个的区别在于是否默认抛出异常:

原文:Java8 Collectors.toMap的坑_八行书-CSDN博客_java8 tomap按照常规思维,往一个map里put一个已经存在的key,会把原有的key对应的value值覆盖,然而通过一次线上问题,发现Java8中的Collectors.toMap反其道而行之,它默认给抛异常,抛异常…?线上业务代码出现Duplicate Key的异常,影响了业务逻辑,查看抛出异常部分的代码,类似以下写法:Map<Integer, String> map = li…[这里是图片002]https://blog.csdn.net/u013805360/article/details/82686009

按照常规思维,往一个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<Integer, String> map = list.stream().collect(Collectors.toMap(Person::getId, Person::getName,(oldValue, newValue) -> newValue));

在这里自己传进去一个方法之后,调用的就是第二个toMap方法了,第二个方法,通过传参来自定义对key发生冲突的处理了。

第三个可以自定义异常方法和Map类型:

public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction,
                                Supplier<M> mapSupplier) {
        BiConsumer<M, T> accumulator
                = (map, element) -> map.merge(keyMapper.apply(element),
                                              valueMapper.apply(element), mergeFunction);
        return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
    }

3. 解决toMap()乱序:使用LinkedHashMap

如果想要输出有序,推荐使用LinkedHashMap

LinkedHashMap除实现HashMap,还维护了一个双向链表。LinkedHashMap为每个Entry添加了前驱和后继,每次向linkedHashMap插入键值对,除了将其插入到哈希表的对应位置之外,还要将其插入到双向循环链表的尾部。
在循环遍历时,HashMap遍历的是tab[]数组,LinkedHashMap遍历的是双向链表,从head开始,即最初的链表顺序。

为保证输出有序,选择LinkedHashMap,具体修改方案如下:

LinkedHashMap<Long, Order> orderMap = orders.stream().collect(Collectors.toMap(Order::getId, order -> order, (oldData, newData) -> newData,LinkedHashMap::new));



4. Collectors.groupingBy() 输出乱序

4.1 场景

想要以id分组输出Map,结果输出是乱序的。

Map<Long, List<OrderVO>> map;
map = list.stream().collect(Collectors.groupingBy(OrderVO::getId));

其原因和Collectors.toMap()类似,查看源码可知Collectors.groupingBy()输出为HashMap。

public static <T, K, A, D>
	Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
								  Collector<? super T, A, D> downstream) {
		return groupingBy(classifier, HashMap::new, downstream);
}

4.2 解决方案

选择以LinkedHashMap输出。

map = list.stream().collect(Collectors.groupingBy(OrderVO::getId, LinkedHashMap::new, Collectors.toList()));
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 8中的Collectors.toMap方法可以将Stream中的元素转换为一个Map对象。该方法有三个参数:keyMapper,valueMapper和mergeFunction。其中,keyMapper用于将Stream中的元素转换为Map的key,valueMapper用于将Stream中的元素转换为Map的value,mergeFunction用于处理当key重复时的冲突情况。 下面是一个例子,假设我们有一个List对象,其中包含多个Person对象,每个Person对象都有一个唯一的id属性和一个name属性。我们可以使用Collectors.toMap方法将List转换为一个以id为key,以Person对象为value的Map对象: ```java List<Person> personList = new ArrayList<>(); // 假设我们已经将多个Person对象添加到了personList中 Map<Integer, Person> personMap = personList.stream() .collect(Collectors.toMap(Person::getId, Function.identity())); ``` 在上面的例子中,Person::getId表示将Person对象的id属性作为Map的key,Function.identity()表示将Person对象本身作为Map的value。如果我们想要将Person对象的name属性作为Map的value,可以这样写: ```java Map<Integer, String> personMap = personList.stream() .collect(Collectors.toMap(Person::getId, Person::getName)); ``` 如果我们的List中有重复的id,那么上面的代码将会抛出IllegalStateException异常。为了避免这种情况,我们可以使用mergeFunction参数来处理冲突。例如,如果我们想要将重复的id的Person对象合并为一个List,可以这样写: ```java Map<Integer, List<Person>> personMap = personList.stream() .collect(Collectors.toMap(Person::getId, Collections::singletonList, (list1, list2) -> { List<Person> resultList = new ArrayList<>(list1); resultList.addAll(list2); return resultList; })); ``` 在上面的代码中,Collections::singletonList表示将Person对象转换为只包含一个元素的List,mergeFunction参数则表示将两个List合并为一个List。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值