Java 8 сборщик для постоянных коллекций Guava?

Мне очень нравятся потоки Java 8 и постоянные коллекции Guava, но я не могу понять, как использовать их вместе.

Например, как реализовать Java 8 Collector, который собирает результаты потока в ImmutableMultimap?

Бонусные баллы: я хотел бы иметь возможность отображать ключи/значения, похожие на Collectors.toMap().

Ответы

Ответ 1

Обновление. Я нашел реализацию, которая, похоже, охватывает все коллекции 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);
    }
}

Основные() выходы:

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

Ресурсы

Ответ 2

Начиная с версии 21 вы можете

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

Ответ 3

Здесь версия, которая будет поддерживать несколько реализаций 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);
}

Ответ 4

Хотя он не отвечает конкретно на этот вопрос, стоит упомянуть, что этот простой шаблон может помочь каждому создавать неизменяемые коллекции Guava из потоков без необходимости в Collector (так как их реализации довольно сложно сделать).

Stream<T> stream = ...

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