在 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 无法得到有序的结果。