22

I find myself wanting a variant of Collectors.toMap which returns an ImmutableMap, such that I can do:

ImmutableMap result = list.stream().collect(MyCollectors.toImmutableMap(
    tuple -> tuple._1(), tuple -> tuple._2());

(where tuple in this particular example is a Scala Tuple2)

I've just learned that such a method will be coming with Java-8 support in Guava 21 (yay!) but that sounds a good 6 months away. Does anyone know of any existing libraries (etc) which might implement this today?

ImmutableMap is not strictly required but seems the best choice as I require: lookup by key, and retaining original iteration order. Immutability is always preferred too.

Note that FluentIterable.toMap(Function) is not sufficient because I need both a key-mapping function as well as a value-mapping function.

5
  • 1
    I'm surprised you didn't see anything on the internet. Searching "guava java 8 collector" in a popular search engine returns a lot of directly usable code, including results directly in the Guava projects (as issues). Commented Dec 29, 2015 at 8:38
  • 1
    We provided collectingAndThen() for this purpose. Commented Dec 29, 2015 at 18:45
  • The answers in the question on ImmutableMultimap indeed answer my immediate questions. However none of the solutions there or that I've now found around the web seem to contain a perfect set of functions to handle ImmutableMap - they lack the ability to merge-duplicate-keys and/or don't throw IllegalStateException (following the convention set by Collectors) Commented Jan 11, 2016 at 20:31
  • 11
    Since Guava 21 there is built-in ImmutableMap.toImmutableMap collector. Commented Apr 3, 2017 at 17:47
  • 1
    See this answer for an example of how to use Guava's toImmutableMap() collector. Commented Apr 27, 2018 at 22:28

2 Answers 2

28

You don't need to write an anonymous class for this collector. You can use Collector.of instead:

public static <T, K, V> Collector<T, ?, ImmutableMap<K,V>> toImmutableMap(
            Function<? super T, ? extends K> keyMapper,
            Function<? super T, ? extends V> valueMapper) {
    return Collector.of(
               ImmutableMap.Builder<K, V>::new,
               (b, e) -> b.put(keyMapper.apply(e), valueMapper.apply(e)),
               (b1, b2) -> b1.putAll(b2.build()),
               ImmutableMap.Builder::build);
}

Or if you don't mind collecting the results into a mutable map first and then copy the data into an immutable map, you can use the built-in toMap collector combined with collectingAndThen:

ImmutableMap<String, String> result = 
     list.stream()
         .collect(collectingAndThen(
             toMap(
                 tuple -> tuple._1(), 
                 tuple -> tuple._2()),
             ImmutableMap::copyOf));
Sign up to request clarification or add additional context in comments.

7 Comments

Perfect :-) I didn't think to look into the interface itself. I definitely prefer the Collector.of form as this will become a utility function with unknown future use (and tracking down sources of memory churn can be difficult later.)
Guava now provides Collectors for their collection types, e.g. ImmutableList.toImmutableMap().
If you don't want to use Guava, you can do the same with Map<String, String> result = list.stream() .collect(collectingAndThen( toMap( tuple -> tuple._1(), tuple -> tuple._2()), Collections::unmodifiableMap));
You should indicate that the second approach does not maintain the original iteration order as requested in the question.
I preferred ImmutableMap::<K, V>builder instead of ImmutableMap.Builder<K, V>::new
|
0

Since I didn't find such a library of collectors yet, I'm sharing my first crack at the specific one I've needed. No bells or whistles here! (Such as handling or merging duplicate keys.)

Please feel free to suggest improvements.

/**
 * A variant of {@link Collectors#toMap(Function, Function)} for immutable maps.
 * <p>
 * Note this variant throws {@link IllegalArgumentException} upon duplicate keys, rather than
 * {@link IllegalStateException}
 * 
 * @param <T> type of the input elements
 * @param <K> output type of the key mapping function
 * @param <V> output type of the value mapping function
 * @param keyMapper  a mapping function to produce keys
 * @param valueMapper a mapping function to produce values
 * 
 * @return a {@code Collector} which collects elements into a {@code Map} whose keys and values
 *         are the result of applying mapping functions to the input elements
 *         
 * @throws IllegalArgumentException upon duplicate keys
 */
public static <T, K, V> Collector<T, ?, ImmutableMap<K,V>> toImmutableMap(
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends V> valueMapper) {
    return new Collector<T, ImmutableMap.Builder<K,V>, ImmutableMap<K,V>>() {

        public Supplier<Builder<K, V>> supplier() {
            return ImmutableMap.Builder::new;
        }

        public BiConsumer<Builder<K, V>, T> accumulator() {
            return (builder, element) -> {
                K key = keyMapper.apply(element);
                V value = valueMapper.apply(element);
                builder.put(key, value);
            };
        }

        public BinaryOperator<Builder<K, V>> combiner() {
            return (b1, b2) -> {
                b1.putAll(b2.build());
                return b1;
            };
        }

        public Function<Builder<K, V>, ImmutableMap<K, V>> finisher() {
            return builder -> builder.build();
        }

        public Set<Collector.Characteristics> characteristics() {
            return ImmutableSet.of();
        }
    };
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.