【问题标题】:Cleanest way to create a Guava Multimap from a Java 8 stream从 Java 8 流创建 Guava Multimap 的最简洁方法
【发布时间】:2014-05-25 01:47:52
【问题描述】:

我有一个List<Foo> 并想要一个Multimap<String, Foo>,我们已经按照FoogetId() 函数对它们进行了分组。

我正在使用 Java 8,它几乎可以做到:

List<Foo> foos = ...
Map<String, List<Foo>> foosById = foos.stream().collect(groupingBy(Foo::getId));

但是,我有大量的代码需要 MultiMap&lt;String, Foo&gt;,所以这并没有为我节省任何东西,我又回到使用 for 循环来创建我的 Multimap。有没有我错过的不错的“功能性”方式?

【问题讨论】:

  • 这不是重复的,因为另一个问题是通过数组(标签)而不是具有单个值(id)的属性来索引 Foo 的属性
  • 从 Guava 21.0 开始,有一个内置的 Multimaps#toMultimapflatteningToMultimap 方法返回收集器。
  • 投票重新打开,因为这不是链接问题的重复。这一个是 API 的直接使用(如已接受的答案中所述),而另一个更多是关于转换和平面映射值,而不是真正关于实际集合本身。
  • @DanielBickler 现在可以再次发布答案,因为此问题已重新打开,我已将您的评论扩展为完整答案:stackoverflow.com/a/68443128/1108305

标签: java java-stream guava


【解决方案1】:

你可以使用 Guava Multimaps factory:

ImmutableMultimap<String, Foo> foosById = Multimaps.index(foos, Foo::getId);

或使用Collector&lt;T, A, R&gt; 接口封装对Multimaps.index 的调用(如下所示,在未优化的幼稚实现中)。

Multimap<String, Foo> collect = foos.stream()
        .collect(MultimapCollector.toMultimap(Foo::getId));

还有Collector:

public class MultimapCollector<T, K, V> implements Collector<T, Multimap<K, V>, Multimap<K, V>> {

    private final Function<T, K> keyGetter;
    private final Function<T, V> valueGetter;

    public MultimapCollector(Function<T, K> keyGetter, Function<T, V> valueGetter) {
        this.keyGetter = keyGetter;
        this.valueGetter = valueGetter;
    }

    public static <T, K, V> MultimapCollector<T, K, V> toMultimap(Function<T, K> keyGetter, Function<T, V> valueGetter) {
        return new MultimapCollector<>(keyGetter, valueGetter);
    }

    public static <T, K, V> MultimapCollector<T, K, T> toMultimap(Function<T, K> keyGetter) {
        return new MultimapCollector<>(keyGetter, v -> v);
    }

    @Override
    public Supplier<Multimap<K, V>> supplier() {
        return ArrayListMultimap::create;
    }

    @Override
    public BiConsumer<Multimap<K, V>, T> accumulator() {
        return (map, element) -> map.put(keyGetter.apply(element), valueGetter.apply(element));
    }

    @Override
    public BinaryOperator<Multimap<K, V>> combiner() {
        return (map1, map2) -> {
            map1.putAll(map2);
            return map1;
        };
    }

    @Override
    public Function<Multimap<K, V>, Multimap<K, V>> finisher() {
        return map -> map;
    }

    @Override
    public Set<Characteristics> characteristics() {
        return ImmutableSet.of(Characteristics.IDENTITY_FINISH);
    }
}

【讨论】:

  • Multimaps.index 返回一个不可变 Multimap,这可能不是你想要的。
  • 同意,multimap 本身是不可变的,但这并不意味着其中包含的对象自动是。如果我们可以在不可变集合中获得对对象的引用,那么没有什么可以阻止我们更改该对象的任何可变状态。因此,对于某些用例,Multimaps.index 仍然不错。我的 2 美分。
  • 在大多数情况下,您应该需要一个不可变的多图。
  • 我写了一个 ImmutableMultimapCollector:medium.com/@robertmassaioli/…
  • 除了不可变之外,值得注意的是Multimaps.index 返回一个ListMultimap。如果您想要SetMultimap,如果您使用Multimaps.index,则需要另一个步骤来转换该值,例如:ImmutableSetMultimap.copyOf(Multimaps.index(foos, Foo::getId))
【解决方案2】:

Guava 21.0 引入了几种返回 Collector 实例的方法,这些方法会将 Stream 转换为 Multimap,按将函数应用于其元素的结果进行分组。这些方法是:

ImmutableListMultimap<String, Foo> foosById = foos.stream().collect(
        ImmutableListMultimap.toImmutableListMultimap(
                Foo::getId, Function.identity()));
ImmutableSetMultimap<String, Foo> foosById = foos.stream().collect(
        ImmutableSetMultimap.toImmutableSetMultimap(
                Foo::getId, Function.identity()));
HashMultimap<String, Foo> foosById = foos.stream().collect(
        Multimaps.toMultimap(
                Foo::getId, Function.identity(), HashMultimap::create)
);

【讨论】:

    猜你喜欢
    • 2023-03-30
    • 1970-01-01
    • 2010-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-27
    相关资源
    最近更新 更多