Bootstrap

Java8 Collectors.toMap()和Collectors.groupingBy()输出乱序

前言

项目中经常在对list集合转map时,输出后的集合是乱序的,但有时候我们希望输出的集合是按照原先list的顺序进行输出,本章内容主要讲解为何list转map后输出乱序,以及如何按顺序输出。

1.Collectors.toMap() 输出乱序

1.1 场景示例

对学生集合根据姓名字段去重,按照原先顺序显示列表,但输出后发现list集合顺序乱了

 List<Student> list = new ArrayList<>();
list = list.stream()
      .collect(Collectors.toMap(Student::getName, Function.identity(),(oldValue,newValue)->newValue))
      .values().stream().collect(Collectors.toList());

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

1.2 Collectors.toMap()

Collectors.toMap()有三个参数,以Collectors.toMap(Student::getName, Function.identity(), (oldValue, newValue) -> newValue)为例,第一个参数Student::getName为Map的key,第二个参数Function.identity()为value,第三个参数(oldValue, newValue) -> newValue表示出现相同的key时,取新key值对应的value值。

点进Collectors.toMap()源码,可以看到,输出的Map是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);
    }

而HashMap输出是乱序的。HashMap中的顺序是根据hash值来排序的。

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

1.3 LinkedHashMap

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

1.4 解决方案

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

list = list.stream()
     .collect(Collectors.toMap(Student::getName, Function.identity(),(oldValue,newValue)->newValue,LinkedHashMap::new))
     .values().stream().collect(Collectors.toList());

list转map后再转为list的写法,以上为写法一
写法二:

list = list.stream()
      .collect(Collectors.toMap(Student::getName, Function.identity(),(oldValue,newValue)->newValue,LinkedHashMap::new))
      .entrySet().stream()
      .map(a -> a.getValue())
      .collect(Collectors.toList());

除了选择LinkedHashMap解决输出乱序的问题,若list集合中有其他规则可以达到顺序的结果也是可以的,如根据list中id的顺序排序等,达到有序的效果。

2.Collectors.groupingBy() 输出乱序

2.1 场景示例

对list集合以姓名进行分组,输出后却是乱序的

Map<String, List<Student>> map = list.stream().collect(Collectors.groupingBy(Student::getName));

其原因和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);
    }

2.2 解决方案

选择以LinkedHashMap输出。

 Map<String, List<Student>> map = list.stream().collect(Collectors.groupingBy(Student::getName,LinkedHashMap::new,Collectors.toList()));
;