--- old/src/share/classes/java/util/stream/Collectors.java 2014-04-23 10:45:54.013306749 +0200 +++ new/src/share/classes/java/util/stream/Collectors.java 2014-04-23 10:45:53.930308286 +0200 @@ -120,17 +120,64 @@ 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 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} that merges the contents of its right + * argument into its left argument, throwing {@code IllegalStateException} + * if duplicate keys are encountered. + * + * @param type of the map keys + * @param type of the map values + * @param type of the map + * {@link Map#merge(Object, Object, BiFunction) Map.merge()} + * @return a merge function for two maps */ - private static BinaryOperator throwingMerger() { - return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }; + private static > + BinaryOperator uniqKeysMapMerger() { + return (m1, m2) -> { + for (Map.Entry 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} 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 type of elements + * @param type of map keys + * @param type of map values + * @return an accumulating consumer + */ + private static + BiConsumer, T> uniqKeysMapAccumulator(Function keyMapper, + Function 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") @@ -1209,7 +1256,10 @@ public static Collector> toMap(Function keyMapper, Function valueMapper) { - return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new); + return new CollectorImpl<>(HashMap::new, + uniqKeysMapAccumulator(keyMapper, valueMapper), + uniqKeysMapMerger(), + CH_ID); } /** @@ -1372,7 +1422,10 @@ public static Collector> toConcurrentMap(Function keyMapper, Function valueMapper) { - return toConcurrentMap(keyMapper, valueMapper, throwingMerger(), ConcurrentHashMap::new); + return new CollectorImpl<>(ConcurrentHashMap::new, + uniqKeysMapAccumulator(keyMapper, valueMapper), + uniqKeysMapMerger(), + CH_CONCURRENT_ID); } /**