Collectors.toMap使用详解

  • Post author:
  • Post category:其他




一、使用规则

在实际项目中我们经常会有 List 转 Map 操作,在过去(JAVA8以前)我们可能使用的是 for 循环遍历的方式,这种方式就不做过多赘述。这里主要讲解使用 Collectors.toMap 方式及使用过程需注意的地方,避免踩坑



二、源码及方法

三个重载的方法如下:

//方法一:2个参数
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);
}
//方法二:3个参数
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);
}
//方法三:4个参数
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);
}

参数含义分别是:

  • keyMapper:Key 的映射函数。
  • valueMapper:Value 的映射函数。
  • mergeFunction:当 Key 冲突时,调用的合并方法。
  • mapSupplier:Map 构造器,在需要返回特定的 Map 时使用。



三、代码示例

首先我们建一个Dept类,属性分别有id,name,group,然后构造一个list:

public class ListToMapDemo {
    public static void main(String[] args) {
        List<Dept> list = new ArrayList<>();
        list.add(new Dept(1,"部门一","1"));
        list.add(new Dept(2,"部门二","2"));
        list.add(new Dept(3,"部门三","3"));
        list.add(new Dept(4,"部门四","4"));
        
    }

    static class Dept{
        private Integer id;
        private String name;
        private String group;

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getGroup() {
            return group;
        }

        public void setGroup(String group) {
            this.group = group;
        }

        public Dept(Integer id, String name, String group) {
            this.id = id;
            this.name = name;
            this.group = group;
        }
 @Override
        public String toString() {
            return "Dept{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", group='" + group + '\'' +
                    '}';
        }
    }
}



1.将list转成以id为key的map,value是id对应的Dept对象

Map<Integer, Dept> map = list.stream().collect(Collectors.toMap(Dept::getId, Function.identity()));
System.out.println(map);
  
 运行结果如下:
 {1=Dept{id=1, name='部门一', group='1'}, 2=Dept{id=2, name='部门二', group='2'}, 
 3=Dept{id=3, name='部门三', group='3'}, 4=Dept{id=4, name='部门四', group='4'}}      



2.假如id存在重复值,则会报错Duplicate key xxx, 解决方案是

只取后一个key及value:

List<Dept> list = new ArrayList<>();
list.add(new Dept(1,"部门一","1"));
list.add(new Dept(2,"部门二","2"));
list.add(new Dept(3,"部门三","3"));
list.add(new Dept(3,"部门四","4"));
Map<Integer, Dept> map = list.stream().collect(Collectors.toMap(Dept::getId, Function.identity(),(oldValue, newValue) -> newValue));
System.out.println(map);

运行结果如下:
{1=Dept{id=1, name='部门一', group='1'}, 2=Dept{id=2, name='部门二', group='2'}, 3=Dept{id=3, name='部门四', group='4'}}

只取前一个key及value:

 Map<Integer, Dept> map = list.stream().collect(Collectors.toMap(Dept::getId,Function.identity(),(oldValue,newValue) -> oldValue));
System.out.println(map);

运行结果如下:
{1=Dept{id=1, name='部门一', group='1'}, 2=Dept{id=2, name='部门二', group='2'},
 3=Dept{id=3, name='部门三', group='3'}}



3.想获得一个id和name对应的Map<Integer, String>

  Map<Integer, String> map = list.stream().collect(Collectors.toMap(Dept::getId,Dept::getName));
  System.out.println(map);
运行结果如下:

{1=部门一, 2=部门二, 3=部门三, 4=部门四}

注意:name可以为空字符串但不能为null,否则会报空指针,解决方案:

Map<Integer, String> map = list.stream().collect(Collectors.toMap(Dept::getId, e -> e.getName() == null ? "" : e.getName()));

假如存在id重复,两个vaue可以这样映射到同一个id:

    Map<Integer, String> map = list.stream().collect(Collectors.toMap(Dept::getId,Dept::getName,(e1, e2)->e1+","+e2));
System.out.println(map);

运行结果如下:
{1=部门一, 2=部门二, 3=部门三,部门四}



4.把Dept集合按照group分组到map中

  Map<String, List<Dept>> map = list.stream().collect(Collectors.groupingBy(Dept::getGroup));
System.out.println(map);


运行结果如下:
{1=[Dept{id=1, name='部门一', group='1'}], 2=[Dept{id=2, name='部门二', group='2'}], 
3=[Dept{id=3, name='部门三', group='3'}, Dept{id=3, name='部门四', group='3'}]}



5.过滤去重,两个List

Map<String, Dept> map2 = list2.stream().collect(Collectors.toMap(Dept::getId,Function.identity()));

//把List1和List2中id重复的Dept对象的name取出来:

List strings = list1.stream().map(Dept::getId).filter(map2::containsKey).map(map2::get).map(Dept::getName).collect(Collectors.toList());

System.out.println(strings);// 输出 [部门2, 部门3]



四、总结

Collectors.toMap 确实带来方便,但是,与此同时,也需要注意两点(你也可以认为是2个坑):

需考虑是否有key重复情况;

需考虑是否有value为null情况。

以上两点在阿里的Java开发手册中也明确指出:

在这里插入图片描述



版权声明:本文为m0_37899908原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。