Guava不可变集合的Java 8收集器?

25
我非常喜欢Java 8的streams和Guava的immutable collections,但我不知道如何将它们结合起来使用。
例如,我该如何实现一个Java 8的Collector,将stream结果收集到ImmutableMultimap中?
额外加分:我想能够提供键/值映射器,类似于Collectors.toMap()的工作方式。

3
这个链接可以帮助你- http://www.jayway.com/2013/11/12/immutable-list-collector-in-java-8/。提示:该链接提供了有关Java 8中的不可变列表收集器的信息。 - Arjit
1
是的,这里有http://www.jayway.com/2014/09/29/java-8-collector-for-gauvas-linkedhashmultimap/,感谢Arijit。 - bobs_007
2
请使用此链接 - http://blog.comsysto.com/2014/11/12/java-8-collectors-for-guava-collections/。 - Arjit
@Arjit 谢谢。我通过试错独立地想出了类似的实现(如下所示)。 - Gili
2
强相关,但尚未出现在相关侧边栏中:如何将Java 8流收集到Guava ImmutableCollection中? - Jeffrey Bosboom
显示剩余2条评论
5个回答

32

从21版本开始,你可以

.collect(ImmutableSet.toImmutableSet())
.collect(ImmutableMap.toImmutableMap())
.collect(Maps.toImmutableEnumMap())
.collect(Sets.toImmutableEnumSet())
.collect(Tables.toTable())
.collect(ImmutableList.toImmutableList())
.collect(Multimaps.toMultimap(...))

8

更新:我发现了一个似乎涵盖了所有Guava集合的实现在https://github.com/yanaga/guava-stream,并尝试在我的库中进行改进在https://bitbucket.org/cowwoc/guava-jdk8/

出于历史原因,我将保留下面的先前回答。


天啊 #@!( 我搞定它了!

这个实现适用于任何Multimap(可变或不可变),而shmosel的解决方案则专注于不可变实现。也就是说,后者可能对于不可变情况更有效率(我没有使用构建器)。

import com.google.common.collect.Multimap;
import java.util.EnumSet;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collector.Characteristics;
import org.bitbucket.cowwoc.preconditions.Preconditions;

/**
 * A Stream collector that returns a Multimap.
 * <p>
 * @author Gili Tzabari
 * @param <T> the type of the input elements
 * @param <K> the type of keys stored in the map
 * @param <V> the type of values stored in the map
 * @param <R> the output type of the collector
 */
public final class MultimapCollector<T, K, V, R extends Multimap<K, V>>
    implements Collector<T, Multimap<K, V>, R>
{
    private final Supplier<Multimap<K, V>> mapSupplier;
    private final Function<? super T, ? extends K> keyMapper;
    private final Function<? super T, ? extends V> valueMapper;
    private final Function<Multimap<K, V>, R> resultMapper;

    /**
     * Creates a new MultimapCollector.
     * <p>
     * @param mapSupplier  a function which returns a new, empty {@code Multimap} into which intermediate results will be
     *                     inserted
     * @param keyMapper    a function that transforms the map keys
     * @param valueMapper  a function that transforms the map values
     * @param resultMapper a function that transforms the intermediate {@code Multimap} into the final result
     * @throws NullPointerException if any of the arguments are null
     */
    public MultimapCollector(Supplier<Multimap<K, V>> mapSupplier,
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends V> valueMapper,
        Function<Multimap<K, V>, R> resultMapper)
    {
        Preconditions.requireThat(mapSupplier, "mapSupplier").isNotNull();
        Preconditions.requireThat(keyMapper, "keyMapper").isNotNull();
        Preconditions.requireThat(valueMapper, "valueMapper").isNotNull();
        Preconditions.requireThat(resultMapper, "resultMapper").isNotNull();

        this.mapSupplier = mapSupplier;
        this.keyMapper = keyMapper;
        this.valueMapper = valueMapper;
        this.resultMapper = resultMapper;
    }

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

    @Override
    public BiConsumer<Multimap<K, V>, T> accumulator()
    {
        return (map, entry) ->
        {
            K key = keyMapper.apply(entry);
            if (key == null)
                throw new IllegalArgumentException("keyMapper(" + entry + ") returned null");
            V value = valueMapper.apply(entry);
            if (value == null)
                throw new IllegalArgumentException("keyMapper(" + entry + ") returned null");
            map.put(key, value);
        };
    }

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

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

    @Override
    public Set<Characteristics> characteristics()
    {
        return EnumSet.noneOf(Characteristics.class);
    }
}

[...]

import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

/**
 * Stream collectors for Guava collections.
 * <p>
 * @author Gili Tzabari
 */
public final class GuavaCollectors
{
    /**
     * Returns a {@code Collector} that accumulates elements into a {@code Multimap}.
     * <p>
     * @param <T>          the type of the input elements
     * @param <K>          the type of the map keys
     * @param <V>          the type of the map values
     * @param <R>          the output type of the collector
     * @param mapSupplier  a function which returns a new, empty {@code Multimap} into which intermediate results will be
     *                     inserted
     * @param keyMapper    a function that transforms the map keys
     * @param valueMapper  a function that transforms the map values
     * @param resultMapper a function that transforms the intermediate {@code Multimap} into the final result
     * @return a {@code Collector} which collects elements into a {@code Multimap} whose keys and values are the result of
     *         applying mapping functions to the input elements
     */
    public static <T, K, V, R extends Multimap<K, V>> Collector<T, ?, R> toMultimap(
        Supplier<Multimap<K, V>> mapSupplier,
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends V> valueMapper,
        Function<Multimap<K, V>, R> resultMapper)
    {
        return new MultimapCollector<>(mapSupplier, keyMapper, valueMapper, resultMapper);
    }

    public static void main(String[] args)
    {
        Multimap<Integer, Double> input = HashMultimap.create();
        input.put(10, 20.0);
        input.put(10, 25.0);
        input.put(50, 60.0);
        System.out.println("input: " + input);
        ImmutableMultimap<Integer, Double> output = input.entries().stream().collect(
            GuavaCollectors.toMultimap(HashMultimap::create,
                entry -> entry.getKey() + 1, entry -> entry.getValue() - 1,
                ImmutableMultimap::copyOf));
        System.out.println("output: " + output);
    }
}

main() 的输出结果:

input: {10=[20.0, 25.0], 50=[60.0]}
output: {51=[59.0], 11=[24.0, 19.0]}

资源


+1 如果您将您的解决方案发布为可重用的库,我们可以将其作为简单的Maven依赖项添加! :-) 这看起来就像我一直在寻找的临时解决方案,直到Guava 21内置此功能(2016年中)之前。 - Luke Usherwood

5
这里是一个支持多个ImmutableMultimap实现的版本。请注意,第一个方法是私有的,因为它需要一个不安全的转换。
@SuppressWarnings("unchecked")
private static <T, K, V, M extends ImmutableMultimap<K, V>> Collector<T, ?, M> toImmutableMultimap(
        Function<? super T, ? extends K> keyFunction,
        Function<? super T, ? extends V> valueFunction,
        Supplier<? extends ImmutableMultimap.Builder<K, V>> builderSupplier) {

    return Collector.of(
            builderSupplier,
            (builder, element) -> builder.put(keyFunction.apply(element), valueFunction.apply(element)),
            (left, right) -> {
                left.putAll(right.build());
                return left;
            },
            builder -> (M)builder.build());
}

public static <T, K, V> Collector<T, ?, ImmutableMultimap<K, V>> toImmutableMultimap(
        Function<? super T, ? extends K> keyFunction,
        Function<? super T, ? extends V> valueFunction) {
    return toImmutableMultimap(keyFunction, valueFunction, ImmutableMultimap::builder);
}

public static <T, K, V> Collector<T, ?, ImmutableListMultimap<K, V>> toImmutableListMultimap(
        Function<? super T, ? extends K> keyFunction,
        Function<? super T, ? extends V> valueFunction) {
    return toImmutableMultimap(keyFunction, valueFunction, ImmutableListMultimap::builder);
}

public static <T, K, V> Collector<T, ?, ImmutableSetMultimap<K, V>> toImmutableSetMultimap(
        Function<? super T, ? extends K> keyFunction,
        Function<? super T, ? extends V> valueFunction) {
    return toImmutableMultimap(keyFunction, valueFunction, ImmutableSetMultimap::builder);
}

好的解决方案,但由于使用了ImmutableMultimap.Builder,它仅限于不可变实现。似乎无法提供一个单一的收集器来处理可变和不可变类型的情况。我的答案支持两种类型,但不使用Builder,因此对于不可变集合可能效率较低。 - Gili
2
@Gili,问题已经明确表明您正在寻找不可变的收集器。您的答案并不支持不可变类型本身;它依赖于可变结果可以随后复制到不可变集合的假设。就性能而言,我的方法对于顺序流应该更有效,而使用可变累加器可能更适用于并行流。 - shmosel

3

对于您的例子,您可以使用Guava的toImmutableMap()收集器。例如:

import static com.google.common.collect.ImmutableMap.toImmutableMap;

ImmutableMap<String, ZipCode> zipCodeForName =
    people.stream()
        .collect(
            toImmutableMap(Person::getName, p -> p.getAddress().getZipCode()));

ImmutableMap<String, Person> personForName =
    people.stream()
        .collect(
            toImmutableMap(Person::getName, p -> p));
最佳答案中包含了其余的Guava不可变集合的API。

2
尽管它没有直接回答这个问题,但值得一提的是,这个简单的模式可以帮助每个人从流中构建Guava的不可变集合,而无需使用Collector(因为它们的实现相当困难)。
Stream<T> stream = ...

ImmutableXxx<T> collection = ImmutableXxx.copyOf(stream.iterator());

这将不支持其他收集器中标准的任何自定义映射或分组函数。 - shmosel

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