src/share/classes/java/util/stream/Collectors.java

Print this page

        

@@ -118,21 +118,68 @@
     static final Set<Collector.Characteristics> CH_NOID = Collections.emptySet();
 
     private Collectors() { }
 
     /**
-     * Returns a merge function, suitable for use in
-     * {@link Map#merge(Object, Object, BiFunction) Map.merge()} or
-     * {@link #toMap(Function, Function, BinaryOperator) toMap()}, which always
-     * throws {@code IllegalStateException}.  This can be used to enforce the
-     * assumption that the elements being collected are distinct.
+     * Construct an {@code IllegalStateException} with appropriate message.
      *
-     * @param <T> the type of input arguments to the merge function
-     * @return a merge function which always throw {@code IllegalStateException}
+     * @param k the duplicate key
+     * @param u 1st value to be accumulated/merged
+     * @param v 2nd value to be accumulated/merged
+     */
+    private static IllegalStateException duplicateKeyException(
+            Object k, Object u, Object v) {
+        return new IllegalStateException(String.format(
+            "Duplicate key %s (attempted merging values %s and %s)",
+            k, u, v));
+    }
+
+    /**
+     * {@code BinaryOperator<Map>} that merges the contents of its right
+     * argument into its left argument, throwing {@code IllegalStateException}
+     * if duplicate keys are encountered.
+     *
+     * @param <K> type of the map keys
+     * @param <V> type of the map values
+     * @param <M> type of the map
+     * {@link Map#merge(Object, Object, BiFunction) Map.merge()}
+     * @return a merge function for two maps
      */
-    private static <T> BinaryOperator<T> throwingMerger() {
-        return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
+    private static <K, V, M extends Map<K,V>>
+    BinaryOperator<M> uniqKeysMapMerger() {
+        return (m1, m2) -> {
+            for (Map.Entry<K,V> e : m2.entrySet()) {
+                K k = e.getKey();
+                V v = Objects.requireNonNull(e.getValue());
+                V u = m1.putIfAbsent(k, v);
+                if (u != null) throw duplicateKeyException(k, u, v);
+            }
+            return m1;
+        };
+    }
+
+    /**
+     * {@code BiConsumer<Map, T>} that accumulates (key, value) pairs
+     * extracted from elements into the map, throwing {@code IllegalStateException}
+     * if duplicate keys are encountered.
+     *
+     * @param keyMapper a function that maps an element into a key
+     * @param valueMapper a function that maps an element into a value
+     * @param <T> type of elements
+     * @param <K> type of map keys
+     * @param <V> type of map values
+     * @return an accumulating consumer
+     */
+    private static <T, K, V>
+    BiConsumer<Map<K, V>, T> uniqKeysMapAccumulator(Function<? super T, ? extends K> keyMapper,
+                                                    Function<? super T, ? extends V> valueMapper) {
+        return (map, element) -> {
+            K k = keyMapper.apply(element);
+            V v = Objects.requireNonNull(valueMapper.apply(element));
+            V u = map.putIfAbsent(k, v);
+            if (u != null) throw duplicateKeyException(k, u, v);
+        };
     }
 
     @SuppressWarnings("unchecked")
     private static <I, R> Function<I, R> castingIdentity() {
         return i -> (R) i;

@@ -1207,11 +1254,14 @@
      * @see #toConcurrentMap(Function, Function)
      */
     public static <T, K, U>
     Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                     Function<? super T, ? extends U> valueMapper) {
-        return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
+        return new CollectorImpl<>(HashMap::new,
+                                   uniqKeysMapAccumulator(keyMapper, valueMapper),
+                                   uniqKeysMapMerger(),
+                                   CH_ID);
     }
 
     /**
      * Returns a {@code Collector} that accumulates elements into a
      * {@code Map} whose keys and values are the result of applying the provided

@@ -1370,11 +1420,14 @@
      * @see #toConcurrentMap(Function, Function, BinaryOperator, Supplier)
      */
     public static <T, K, U>
     Collector<T, ?, ConcurrentMap<K,U>> toConcurrentMap(Function<? super T, ? extends K> keyMapper,
                                                         Function<? super T, ? extends U> valueMapper) {
-        return toConcurrentMap(keyMapper, valueMapper, throwingMerger(), ConcurrentHashMap::new);
+        return new CollectorImpl<>(ConcurrentHashMap::new,
+                                   uniqKeysMapAccumulator(keyMapper, valueMapper),
+                                   uniqKeysMapMerger(),
+                                   CH_CONCURRENT_ID);
     }
 
     /**
      * Returns a concurrent {@code Collector} that accumulates elements into a
      * {@code ConcurrentMap} whose keys and values are the result of applying