I really like Java 8 streams and Guava\'s immutable collections, but I can\'t figure out how to use the two together.
For example, how do I implement a Java 8 Collec
Update: I found an implementation that seems to cover all Guava collections at https://github.com/yanaga/guava-stream and attempted to improve upon it in my own library at https://bitbucket.org/cowwoc/guava-jdk8/
I'm leaving the previous answer below, for historical reasons.
Holy #@!( I got it!
This implementation works for any Multimap (mutable or immutable) whereas shmosel's solution focuses on immutable implementations. That said, the latter might be more efficient for the immutable case (I don't use a builder).
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.
*
* @author Gili Tzabari
* @param the type of the input elements
* @param the type of keys stored in the map
* @param the type of values stored in the map
* @param the output type of the collector
*/
public final class MultimapCollector>
implements Collector, R>
{
private final Supplier> mapSupplier;
private final Function super T, ? extends K> keyMapper;
private final Function super T, ? extends V> valueMapper;
private final Function, R> resultMapper;
/**
* Creates a new MultimapCollector.
*
* @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> mapSupplier,
Function super T, ? extends K> keyMapper,
Function super T, ? extends V> valueMapper,
Function, 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> supplier()
{
return mapSupplier;
}
@Override
public BiConsumer, 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> combiner()
{
return (left, right) ->
{
left.putAll(right);
return left;
};
}
@Override
public Function, R> finisher()
{
return resultMapper;
}
@Override
public Set 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.
*
* @author Gili Tzabari
*/
public final class GuavaCollectors
{
/**
* Returns a {@code Collector} that accumulates elements into a {@code Multimap}.
*
* @param the type of the input elements
* @param the type of the map keys
* @param the type of the map values
* @param 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 > Collector toMultimap(
Supplier> mapSupplier,
Function super T, ? extends K> keyMapper,
Function super T, ? extends V> valueMapper,
Function, R> resultMapper)
{
return new MultimapCollector<>(mapSupplier, keyMapper, valueMapper, resultMapper);
}
public static void main(String[] args)
{
Multimap input = HashMultimap.create();
input.put(10, 20.0);
input.put(10, 25.0);
input.put(50, 60.0);
System.out.println("input: " + input);
ImmutableMultimap 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() outputs:
input: {10=[20.0, 25.0], 50=[60.0]}
output: {51=[59.0], 11=[24.0, 19.0]}