在Java 8中,如何使用惯用方式从Stream创建多值Map?

20

有没有一种优雅的方法可以使用Java 8的流API初始化和填充多值Map<K, Collection<V>>

我知道可以使用Collectors.toMap(..)功能创建单值Map<K, V>

Stream<Person> persons = fetchPersons();
Map<String, Person> personsByName = persons.collect(Collectors.toMap(Person::getName, Function.identity()));

不幸的是,对于可能存在非唯一键(如人名)的情况,该方法效果不佳。

另一方面,可以使用Map.compute(K, BiFunction<? super K,? super V,? extends V>>来填充一个多值的Map<K, Collection<V>>

Stream<Person> persons = fetchPersons();
Map<String, Set<Person>> personsByName = new HashMap<>();
persons.forEach(person -> personsByName.compute(person.getName(), (name, oldValue) -> {
    Set<Person> result = (oldValue== null) ? new HashSet<>() : oldValue;
    result.add(person);
    return result;
}));

有没有更加简洁的方法来完成这个任务,比如说一条语句中完成Map的初始化和填充?


4
这不是 groupingBy 的一个典型应用场景吗? - Patrick Parker
@PatrickParker 是的,你和 Holger 都是对的。我从来没有经常使用过那种方法。 - errantlinguist
@Patrick Parker:是的,这是groupingBy的一个完美应用案例,它是computeIfAbsent的实际应用。当然,在应用程序方面,使用groupingBy更可取。 - Holger
1个回答

35

如果您使用 forEach,使用 computeIfAbsent 要比使用 compute 简单得多:

Map<String, Set<Person>> personsByName = new HashMap<>();
persons.forEach(person ->
    personsByName.computeIfAbsent(person.getName(), key -> new HashSet<>()).add(person));

然而,在使用Stream API时,最好使用collect。在这种情况下,请使用groupingBy而不是toMap

Map<String, Set<Person>> personsByName =
    persons.collect(Collectors.groupingBy(Person::getName, Collectors.toSet());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接