Java8 教程

Java8 将结果收集到Map中

在 Java8 中,要将结果收集到 Map 中,需要用到 Collectors.toMap 方法,方法定义如下:

// 返回一个 Collector ,它将元素累加到一个 Map ,其键和值是将所提供的映射函数应用于输入元素的结果
static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper,
       Function<? super T,? extends U> valueMapper)    
// 返回一个 Collector ,它将元素累加到 Map ,其键和值是将提供的映射函数应用于输入元素的结果
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)
// 返回一个 Collector ,它将元素累加到一个 Map ,其键和值是将所提供的映射函数应用于输入元素的结果
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)

假设你有一个 Stream<Person> 对象,并且希望将其中的元素收集到一个 map 中,这样你随后就可以通过它们的 ID 来进行查找。Collectors.toMap 方法有两个函数参数,分别用来生成 map 的键和值。例如:

package com.hxstrive.jdk8.stream_api.collectors;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * toMap 收集到 Map
 * @author hxstrive.com
 */
public class CollectorsDemo1 {

    public static void main(String[] args) {
        List<Person> list = Arrays.asList(new Person(100, "张三"),
                new Person(200, "李四"),
                new Person(300, "王五"));

        Map<Integer,Person> map = list.stream().collect(Collectors.toMap(p -> {
            return p.id;
        }, p -> {
            return p;
        }));
        System.out.println(map);
        //输出:
        //{100=Person{id=100, name='张三'}, 200=Person{id=200, name='李四'}, 300=Person{id=300, name='王五'}}
    }

    static class Person {
        private int id;
        private String name;

        public Person(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public int getId() {
            return id;
        }

        @Override
        public String toString() {
            return "Person{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
}

一般来说,值应该是实际的元素,使用 Function.identity() 作为第二个函数。例如:

List<Person> list = Arrays.asList(new Person(100, "张三"),
        new Person(200, "李四"),
        new Person(300, "王五"));

Map<Integer,Person> map = list.stream().collect(Collectors.toMap(Person::getId, Function.identity()));
System.out.println(map);
//输出:
//{100=Person{id=100, name='张三'}, 200=Person{id=200, name='李四'}, 300=Person{id=300, name='王五'}}

如果有多个元素拥有相同的键,那么收集方法会抛出一个 IllegalStateException 异常。例如:

List<Person> list = Arrays.asList(new Person(100, "张三"),
        new Person(200, "李四"),
        new Person(300, "王五-old"),
        new Person(300, "王五-new"));

Map<Integer,Person> map = list.stream().collect(Collectors.toMap(Person::getId, Function.identity()));
System.out.println(map);
//输出:
//Exception in thread "main" java.lang.IllegalStateException:
// Duplicate key 300 (attempted merging values Person{id=300, name='王五-old'} and Person{id=300, name='王五-new'})

不过,你可以通过提供第三个函数参数,根据已有的值和新值来决定键的值,从而重写该行为。你的函数可以返回已有值、新值,或者二者都返回。例如:

List<Person> list = Arrays.asList(new Person(100, "张三"),
        new Person(200, "李四"),
        new Person(300, "王五-old"),
        new Person(300, "王五-new"));

Map<Integer,Person> map = list.stream().collect(Collectors.toMap(Person::getId, Function.identity(),
        // 如果有重复的key, 则用后面的覆盖前面的,即返回最新的值
        (oldVal, newVal) -> newVal));
System.out.println(map);
//输出:
//{100=Person{id=100, name='张三'}, 200=Person{id=200, name='李四'}, 300=Person{id=300, name='王五-new'}}

假设我们希望知道指定 Key 拥有的所有值集合,那么就需要一个 Map<String,Set<Person>> 对象。例如:

List<Person> list = Arrays.asList(new Person(100, "张三"),
        new Person(200, "李四"),
        new Person(300, "王五-old"),
        new Person(300, "王五-new"));

Map<Integer,Set<Person>> map = list.stream().collect(Collectors.toMap(Person::getId, Collections::singleton,
        // 如果有重复的key, 则用 set 进行存放
        (oldVal, newVal) -> {
            Set<Person> set = new HashSet<>(oldVal);
            set.addAll(newVal);
            return set;
        }));
System.out.println(map);
//输出:
//{100=[Person{id=100, name='张三'}], 200=[Person{id=200, name='李四'}],
//  300=[Person{id=300, name='王五-old'}, Person{id=300, name='王五-new'}]}

如果你希望得到一个 TreeMap,那么你需要提供一个构造函数作为第 4 个参数。例如:

List<Person> list = Arrays.asList(new Person(100, "张三"),
        new Person(200, "李四"),
        new Person(300, "王五-old"),
        new Person(300, "王五-new"));

Map<Integer,Set<Person>> map = list.stream().collect(Collectors.toMap(Person::getId, Collections::singleton,
        // 如果有重复的key, 则用 set 进行存放
        (oldVal, newVal) -> {
            Set<Person> set = new HashSet<>(oldVal);
            set.addAll(newVal);
            return set;
        }, TreeMap::new));
System.out.println(map.getClass());
System.out.println(map);
//输出:
//class java.util.TreeMap
//{100=[Person{id=100, name='张三'}], 200=[Person{id=200, name='李四'}],
//  300=[Person{id=300, name='王五-old'}, Person{id=300, name='王五-new'}]}

注意:对于 toMap 方法的每种形式,都有一个对应的 toConcurrentMap 方法,用来产生一个并发的、线程安全的 Map。在并行收集过程中应当只使用一个并发的 map。当在并行流中使用并发 map 时,一个共享的 map要比合并 map 效率更高,使用共享的 map 无法得到有序的结果。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号