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