From 24130de9e301e9cc3d599e7027fd7cb1164b6bd9 Mon Sep 17 00:00:00 2001 From: mduigou Date: Mon, 29 Apr 2013 22:03:04 -0700 Subject: [PATCH] 8011917: Add java.util.stream.Collectors utilities Reviewed-by: darcy, mduigou Contributed-by: Brian Goetz --- .../classes/java/util/stream/Collectors.java | 1320 +++++++++++++++++ 1 file changed, 1320 insertions(+) create mode 100644 src/share/classes/java/util/stream/Collectors.java diff --git a/src/share/classes/java/util/stream/Collectors.java b/src/share/classes/java/util/stream/Collectors.java new file mode 100644 index 000000000..0d14c888e --- /dev/null +++ b/src/share/classes/java/util/stream/Collectors.java @@ -0,0 +1,1320 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package java.util.stream; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Comparators; +import java.util.DoubleSummaryStatistics; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IntSummaryStatistics; +import java.util.Iterator; +import java.util.List; +import java.util.LongSummaryStatistics; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; + +/** + * Implementations of {@link Collector} that implement various useful reduction + * operations, such as accumulating elements into collections, summarizing + * elements according to various criteria, etc. + * + *

The following are examples of using the predefined {@code Collector} + * implementations in {@link Collectors} with the {@code Stream} API to perform + * mutable reduction tasks: + * + *

{@code
+ *     // Accumulate elements into a List
+ *     List list = people.collect(Collectors.toList());
+ *
+ *     // Accumulate elements into a TreeSet
+ *     List list = people.collect(Collectors.toCollection(TreeSet::new));
+ *
+ *     // Convert elements to strings and concatenate them, separated by commas
+ *     String joined = stream.map(Object::toString)
+ *                           .collect(Collectors.toStringJoiner(", "))
+ *                           .toString();
+ *
+ *     // Find highest-paid employee
+ *     Employee highestPaid = employees.stream()
+ *                                     .collect(Collectors.maxBy(Comparators.comparing(Employee::getSalary)));
+ *
+ *     // Group employees by department
+ *     Map> byDept
+ *         = employees.stream()
+ *                    .collect(Collectors.groupingBy(Employee::getDepartment));
+ *
+ *     // Find highest-paid employee by department
+ *     Map highestPaidByDept
+ *         = employees.stream()
+ *                    .collect(Collectors.groupingBy(Employee::getDepartment,
+ *                                                   Collectors.maxBy(Comparators.comparing(Employee::getSalary))));
+ *
+ *     // Partition students into passing and failing
+ *     Map> passingFailing =
+ *         students.stream()
+ *                 .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD);
+ *
+ * }
+ * + * TODO explanation of parallel collection + * + * @since 1.8 + */ +public final class Collectors { + + private static final Set CH_CONCURRENT + = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.CONCURRENT, + Collector.Characteristics.STRICTLY_MUTATIVE, + Collector.Characteristics.UNORDERED)); + private static final Set CH_STRICT + = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.STRICTLY_MUTATIVE)); + private static final Set CH_STRICT_UNORDERED + = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.STRICTLY_MUTATIVE, + Collector.Characteristics.UNORDERED)); + + 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. + * + * @param the type of input arguments to the merge function + * @return a merge function which always throw {@code IllegalStateException} + * + * @see #firstWinsMerger() + * @see #lastWinsMerger() + */ + public static BinaryOperator throwingMerger() { + return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }; + } + + /** + * Returns a merge function, suitable for use in + * {@link Map#merge(Object, Object, BiFunction) Map.merge()} or + * {@link #toMap(Function, Function, BinaryOperator) toMap()}, + * which implements a "first wins" policy. + * + * @param the type of input arguments to the merge function + * @return a merge function which always returns its first argument + * @see #lastWinsMerger() + * @see #throwingMerger() + */ + public static BinaryOperator firstWinsMerger() { + return (u,v) -> u; + } + + /** + * Returns a merge function, suitable for use in + * {@link Map#merge(Object, Object, BiFunction) Map.merge()} or + * {@link #toMap(Function, Function, BinaryOperator) toMap()}, + * which implements a "last wins" policy. + * + * @param the type of input arguments to the merge function + * @return a merge function which always returns its second argument + * @see #firstWinsMerger() + * @see #throwingMerger() + */ + public static BinaryOperator lastWinsMerger() { + return (u,v) -> v; + } + + /** + * Simple implementation class for {@code Collector}. + * + * @param the type of elements to be collected + * @param the type of the result + */ + private static final class CollectorImpl implements Collector { + private final Supplier resultSupplier; + private final BiFunction accumulator; + private final BinaryOperator combiner; + private final Set characteristics; + + CollectorImpl(Supplier resultSupplier, + BiFunction accumulator, + BinaryOperator combiner, + Set characteristics) { + this.resultSupplier = resultSupplier; + this.accumulator = accumulator; + this.combiner = combiner; + this.characteristics = characteristics; + } + + CollectorImpl(Supplier resultSupplier, + BiFunction accumulator, + BinaryOperator combiner) { + this(resultSupplier, accumulator, combiner, Collections.emptySet()); + } + + @Override + public BiFunction accumulator() { + return accumulator; + } + + @Override + public Supplier resultSupplier() { + return resultSupplier; + } + + @Override + public BinaryOperator combiner() { + return combiner; + } + + @Override + public Set characteristics() { + return characteristics; + } + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a + * new {@code Collection}, in encounter order. The {@code Collection} is + * created by the provided factory. + * + * @param the type of the input elements + * @param the type of the resulting {@code Collection} + * @param collectionFactory a {@code Supplier} which returns a new, empty + * {@code Collection} of the appropriate type + * @return a {@code Collector} which collects all the input elements into a + * {@code Collection}, in encounter order + */ + public static > + Collector toCollection(Supplier collectionFactory) { + return new CollectorImpl<>(collectionFactory, + (r, t) -> { r.add(t); return r; }, + (r1, r2) -> { r1.addAll(r2); return r1; }, + CH_STRICT); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a + * new {@code List}. There are no guarantees on the type, mutability, + * serializability, or thread-safety of the {@code List} returned. + * + * @param the type of the input elements + * @return a {@code Collector} which collects all the input elements into a + * {@code List}, in encounter order + */ + public static + Collector> toList() { + BiFunction, T, List> accumulator = (list, t) -> { + switch (list.size()) { + case 0: + return Collections.singletonList(t); + case 1: + List newList = new ArrayList<>(); + newList.add(list.get(0)); + newList.add(t); + return newList; + default: + list.add(t); + return list; + } + }; + BinaryOperator> combiner = (left, right) -> { + switch (left.size()) { + case 0: + return right; + case 1: + List newList = new ArrayList<>(left.size() + right.size()); + newList.addAll(left); + newList.addAll(right); + return newList; + default: + left.addAll(right); + return left; + } + }; + return new CollectorImpl<>(Collections::emptyList, accumulator, combiner); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a + * new {@code Set}. There are no guarantees on the type, mutability, + * serializability, or thread-safety of the {@code Set} returned. + * + *

This is an {@link Collector.Characteristics#UNORDERED unordered} + * Collector. + * + * @param the type of the input elements + * @return a {@code Collector} which collects all the input elements into a + * {@code Set} + */ + public static + Collector> toSet() { + return new CollectorImpl<>((Supplier>) HashSet::new, + (r, t) -> { r.add(t); return r; }, + (r1, r2) -> { r1.addAll(r2); return r1; }, + CH_STRICT_UNORDERED); + } + + /** + * Returns a {@code Collector} that concatenates the input elements into a + * new {@link StringBuilder}. + * + * @return a {@code Collector} which collects String elements into a + * {@code StringBuilder}, in encounter order + */ + public static Collector toStringBuilder() { + return new CollectorImpl<>(StringBuilder::new, + (r, t) -> { r.append(t); return r; }, + (r1, r2) -> { r1.append(r2); return r1; }, + CH_STRICT); + } + + /** + * Returns a {@code Collector} that concatenates the input elements into a + * new {@link StringJoiner}, using the specified delimiter. + * + * @param delimiter the delimiter to be used between each element + * @return A {@code Collector} which collects String elements into a + * {@code StringJoiner}, in encounter order + */ + public static Collector toStringJoiner(CharSequence delimiter) { + BinaryOperator merger = (sj, other) -> { + if (other.length() > 0) + sj.add(other.toString()); + return sj; + }; + return new CollectorImpl<>(() -> new StringJoiner(delimiter), + (r, t) -> { r.add(t); return r; }, + merger, CH_STRICT); + } + + /** + * {@code BinaryOperator} that merges the contents of its right + * argument into its left argument, using the provided merge function to + * handle duplicate keys. + * + * @param type of the map keys + * @param type of the map values + * @param type of the map + * @param mergeFunction A merge function suitable for + * {@link Map#merge(Object, Object, BiFunction) Map.merge()} + * @return a merge function for two maps + */ + private static > + BinaryOperator mapMerger(BinaryOperator mergeFunction) { + return (m1, m2) -> { + for (Map.Entry e : m2.entrySet()) + m1.merge(e.getKey(), e.getValue(), mergeFunction); + return m1; + }; + } + + /** + * Adapts a {@code Collector} to a {@code Collector} by applying + * a mapping function to each input element before accumulation. + * + * @apiNote + * The {@code mapping()} collectors are most useful when used in a + * multi-level reduction, downstream of {@code groupingBy} or + * {@code partitioningBy}. For example, given a stream of + * {@code Person}, to accumulate the set of last names in each city: + *

{@code
+     *     Map> lastNamesByCity
+     *         = people.stream().collect(groupingBy(Person::getCity,
+     *                                              mapping(Person::getLastName, toSet())));
+     * }
+ * + * @param the type of the input elements + * @param type of elements accepted by downstream collector + * @param result type of collector + * @param mapper a function to be applied to the input elements + * @param downstream a collector which will accept mapped values + * @return a collector which applies the mapping function to the input + * elements and provides the mapped results to the downstream collector + */ + public static Collector + mapping(Function mapper, Collector downstream) { + BiFunction downstreamAccumulator = downstream.accumulator(); + return new CollectorImpl<>(downstream.resultSupplier(), + (r, t) -> downstreamAccumulator.apply(r, mapper.apply(t)), + downstream.combiner(), downstream.characteristics()); + } + + /** + * Returns a {@code Collector} that counts the number of input + * elements. + * + * @implSpec + * This produces a result equivalent to: + *
{@code
+     *     reducing(0L, e -> 1L, Long::sum)
+     * }
+ * + * @param the type of the input elements + * @return a {@code Collector} that counts the input elements + */ + public static Collector + counting() { + return reducing(0L, e -> 1L, Long::sum); + } + + /** + * Returns a {@code Collector} that produces the minimal element + * according to a given {@code Comparator}. + * + * @implSpec + * This produces a result equivalent to: + *
{@code
+     *     reducing(Comparators.lesserOf(comparator))
+     * }
+ * + * @param the type of the input elements + * @param comparator a {@code Comparator} for comparing elements + * @return a {@code Collector} that produces the minimal value + */ + public static Collector + minBy(Comparator comparator) { + return reducing(Comparators.lesserOf(comparator)); + } + + /** + * Returns a {@code Collector} that produces the maximal element + * according to a given {@code Comparator}. + * + * @implSpec + * This produces a result equivalent to: + *
{@code
+     *     reducing(Comparators.greaterOf(comparator))
+     * }
+ * + * @param the type of the input elements + * @param comparator a {@code Comparator} for comparing elements + * @return a {@code Collector} that produces the maximal value + */ + public static Collector + maxBy(Comparator comparator) { + return reducing(Comparators.greaterOf(comparator)); + } + + /** + * Returns a {@code Collector} that produces the sum of a + * long-valued function applied to the input element. + * + * @implSpec + * This produces a result equivalent to: + *
{@code
+     *     reducing(0L, mapper, Long::sum)
+     * }
+ * + * @param the type of the input elements + * @param mapper a function extracting the property to be summed + * @return a {@code Collector} that produces the sum of a derived property + */ + public static Collector + sumBy(Function mapper) { + return reducing(0L, mapper, Long::sum); + } + + /** + * Returns a {@code Collector} which performs a reduction of its + * input elements under a specified {@code BinaryOperator}. + * + * @apiNote + * The {@code reducing()} collectors are most useful when used in a + * multi-level reduction, downstream of {@code groupingBy} or + * {@code partitioningBy}. To perform a simple reduction on a stream, + * use {@link Stream#reduce(BinaryOperator)} instead. + * + * @param element type for the input and output of the reduction + * @param identity the identity value for the reduction (also, the value + * that is returned when there are no input elements) + * @param op a {@code BinaryOperator} used to reduce the input elements + * @return a {@code Collector} which implements the reduction operation + * + * @see #reducing(BinaryOperator) + * @see #reducing(Object, Function, BinaryOperator) + */ + public static Collector + reducing(T identity, BinaryOperator op) { + return new CollectorImpl<>(() -> identity, (r, t) -> (r == null ? t : op.apply(r, t)), op); + } + + /** + * Returns a {@code Collector} which performs a reduction of its + * input elements under a specified {@code BinaryOperator}. + * + * @apiNote + * The {@code reducing()} collectors are most useful when used in a + * multi-level reduction, downstream of {@code groupingBy} or + * {@code partitioningBy}. To perform a simple reduction on a stream, + * use {@link Stream#reduce(BinaryOperator)} instead. + * + *

For example, given a stream of {@code Person}, to calculate tallest + * person in each city: + *

{@code
+     *     Comparator byHeight = Comparators.comparing(Person::getHeight);
+     *     BinaryOperator tallerOf = Comparators.greaterOf(byHeight);
+     *     Map tallestByCity
+     *         = people.stream().collect(groupingBy(Person::getCity, reducing(tallerOf)));
+     * }
+ * + * @implSpec + * The default implementation is equivalent to: + *
{@code
+     *     reducing(null, op);
+     * }
+ * + * @param element type for the input and output of the reduction + * @param op a {@code BinaryOperator} used to reduce the input elements + * @return a {@code Collector} which implements the reduction operation + * + * @see #reducing(Object, BinaryOperator) + * @see #reducing(Object, Function, BinaryOperator) + */ + public static Collector + reducing(BinaryOperator op) { + return reducing(null, op); + } + + /** + * Returns a {@code Collector} which performs a reduction of its + * input elements under a specified mapping function and + * {@code BinaryOperator}. This is a generalization of + * {@link #reducing(Object, BinaryOperator)} which allows a transformation + * of the elements before reduction. + * + * @apiNote + * The {@code reducing()} collectors are most useful when used in a + * multi-level reduction, downstream of {@code groupingBy} or + * {@code partitioningBy}. To perform a simple reduction on a stream, + * use {@link Stream#reduce(BinaryOperator)} instead. + * + *

For example, given a stream of {@code Person}, to calculate the longest + * last name of residents in each city: + *

{@code
+     *     Comparator byLength = Comparators.comparing(String::length);
+     *     BinaryOperator longerOf = Comparators.greaterOf(byLength);
+     *     Map longestLastNameByCity
+     *         = people.stream().collect(groupingBy(Person::getCity,
+     *                                              reducing(Person::getLastName, longerOf)));
+     * }
+ * + * @param the type of the input elements + * @param the type of the mapped values + * @param identity the identity value for the reduction (also, the value + * that is returned when there are no input elements) + * @param mapper a mapping function to apply to each input value + * @param op a {@code BinaryOperator} used to reduce the mapped values + * @return a {@code Collector} implementing the map-reduce operation + * + * @see #reducing(Object, BinaryOperator) + * @see #reducing(BinaryOperator) + */ + public static + Collector reducing(U identity, + Function mapper, + BinaryOperator op) { + return new CollectorImpl<>(() -> identity, + (r, t) -> (r == null ? mapper.apply(t) : op.apply(r, mapper.apply(t))), + op); + } + + /** + * Returns a {@code Collector} implementing a "group by" operation on + * input elements of type {@code T}, grouping elements according to a + * classification function. + * + *

The classification function maps elements to some key type {@code K}. + * The collector produces a {@code Map>} whose keys are the + * values resulting from applying the classification function to the input + * elements, and whose corresponding values are {@code List}s containing the + * input elements which map to the associated key under the classification + * function. + * + *

There are no guarantees on the type, mutability, serializability, or + * thread-safety of the {@code Map} or {@code List} objects returned. + * @implSpec + * This produces a result similar to: + *

{@code
+     *     groupingBy(classifier, toList());
+     * }
+ * + * @param the type of the input elements + * @param the type of the keys + * @param classifier the classifier function mapping input elements to keys + * @return a {@code Collector} implementing the group-by operation + * + * @see #groupingBy(Function, Collector) + * @see #groupingBy(Function, Supplier, Collector) + * @see #groupingByConcurrent(Function) + */ + public static + Collector>> groupingBy(Function classifier) { + return groupingBy(classifier, HashMap::new, toList()); + } + + /** + * Returns a {@code Collector} implementing a cascaded "group by" operation + * on input elements of type {@code T}, grouping elements according to a + * classification function, and then performing a reduction operation on + * the values associated with a given key using the specified downstream + * {@code Collector}. + * + *

The classification function maps elements to some key type {@code K}. + * The downstream collector operates on elements of type {@code T} and + * produces a result of type {@code D}. The resulting collector produces a + * {@code Map}. + * + *

There are no guarantees on the type, mutability, + * serializability, or thread-safety of the {@code Map} returned. + * + *

For example, to compute the set of last names of people in each city: + *

{@code
+     *     Map> namesByCity
+     *         = people.stream().collect(groupingBy(Person::getCity,
+     *                                              mapping(Person::getLastName, toSet())));
+     * }
+ * + * @param the type of the input elements + * @param the type of the keys + * @param the result type of the downstream reduction + * @param classifier a classifier function mapping input elements to keys + * @param downstream a {@code Collector} implementing the downstream reduction + * @return a {@code Collector} implementing the cascaded group-by operation + * @see #groupingBy(Function) + * + * @see #groupingBy(Function, Supplier, Collector) + * @see #groupingByConcurrent(Function, Collector) + */ + public static + Collector> groupingBy(Function classifier, + Collector downstream) { + return groupingBy(classifier, HashMap::new, downstream); + } + + /** + * Returns a {@code Collector} implementing a cascaded "group by" operation + * on input elements of type {@code T}, grouping elements according to a + * classification function, and then performing a reduction operation on + * the values associated with a given key using the specified downstream + * {@code Collector}. The {@code Map} produced by the Collector is created + * with the supplied factory function. + * + *

The classification function maps elements to some key type {@code K}. + * The downstream collector operates on elements of type {@code T} and + * produces a result of type {@code D}. The resulting collector produces a + * {@code Map}. + * + *

For example, to compute the set of last names of people in each city, + * where the city names are sorted: + *

{@code
+     *     Map> namesByCity
+     *         = people.stream().collect(groupingBy(Person::getCity, TreeMap::new,
+     *                                              mapping(Person::getLastName, toSet())));
+     * }
+ * + * @param the type of the input elements + * @param the type of the keys + * @param the result type of the downstream reduction + * @param the type of the resulting {@code Map} + * @param classifier a classifier function mapping input elements to keys + * @param downstream a {@code Collector} implementing the downstream reduction + * @param mapFactory a function which, when called, produces a new empty + * {@code Map} of the desired type + * @return a {@code Collector} implementing the cascaded group-by operation + * + * @see #groupingBy(Function, Collector) + * @see #groupingBy(Function) + * @see #groupingByConcurrent(Function, Supplier, Collector) + */ + public static > + Collector groupingBy(Function classifier, + Supplier mapFactory, + Collector downstream) { + Supplier downstreamSupplier = downstream.resultSupplier(); + BiFunction downstreamAccumulator = downstream.accumulator(); + BiFunction accumulator = (m, t) -> { + K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key"); + D oldContainer = m.computeIfAbsent(key, k -> downstreamSupplier.get()); + D newContainer = downstreamAccumulator.apply(oldContainer, t); + if (newContainer != oldContainer) + m.put(key, newContainer); + return m; + }; + return new CollectorImpl<>(mapFactory, accumulator, mapMerger(downstream.combiner()), CH_STRICT); + } + + /** + * Returns a {@code Collector} implementing a concurrent "group by" + * operation on input elements of type {@code T}, grouping elements + * according to a classification function. + * + *

This is a {@link Collector.Characteristics#CONCURRENT concurrent} and + * {@link Collector.Characteristics#UNORDERED unordered} Collector. + * + *

The classification function maps elements to some key type {@code K}. + * The collector produces a {@code ConcurrentMap>} whose keys are the + * values resulting from applying the classification function to the input + * elements, and whose corresponding values are {@code List}s containing the + * input elements which map to the associated key under the classification + * function. + * + *

There are no guarantees on the type, mutability, or serializability + * of the {@code Map} or {@code List} objects returned, or of the + * thread-safety of the {@code List} objects returned. + * @implSpec + * This produces a result similar to: + *

{@code
+     *     groupingByConcurrent(classifier, toList());
+     * }
+ * + * @param the type of the input elements + * @param the type of the keys + * @param classifier a classifier function mapping input elements to keys + * @return a {@code Collector} implementing the group-by operation + * + * @see #groupingBy(Function) + * @see #groupingByConcurrent(Function, Collector) + * @see #groupingByConcurrent(Function, Supplier, Collector) + */ + public static + Collector>> groupingByConcurrent(Function classifier) { + return groupingByConcurrent(classifier, ConcurrentHashMap::new, toList()); + } + + /** + * Returns a {@code Collector} implementing a concurrent cascaded "group by" + * operation on input elements of type {@code T}, grouping elements + * according to a classification function, and then performing a reduction + * operation on the values associated with a given key using the specified + * downstream {@code Collector}. + * + *

This is a {@link Collector.Characteristics#CONCURRENT concurrent} and + * {@link Collector.Characteristics#UNORDERED unordered} Collector. + * + *

The classification function maps elements to some key type {@code K}. + * The downstream collector operates on elements of type {@code T} and + * produces a result of type {@code D}. The resulting collector produces a + * {@code Map}. + * + *

For example, to compute the set of last names of people in each city, + * where the city names are sorted: + *

{@code
+     *     ConcurrentMap> namesByCity
+     *         = people.stream().collect(groupingByConcurrent(Person::getCity, TreeMap::new,
+     *                                                        mapping(Person::getLastName, toSet())));
+     * }
+ * + * @param the type of the input elements + * @param the type of the keys + * @param the result type of the downstream reduction + * @param classifier a classifier function mapping input elements to keys + * @param downstream a {@code Collector} implementing the downstream reduction + * @return a {@code Collector} implementing the cascaded group-by operation + * + * @see #groupingBy(Function, Collector) + * @see #groupingByConcurrent(Function) + * @see #groupingByConcurrent(Function, Supplier, Collector) + */ + public static + Collector> groupingByConcurrent(Function classifier, + Collector downstream) { + return groupingByConcurrent(classifier, ConcurrentHashMap::new, downstream); + } + + /** + * Returns a concurrent {@code Collector} implementing a cascaded "group by" + * operation on input elements of type {@code T}, grouping elements + * according to a classification function, and then performing a reduction + * operation on the values associated with a given key using the specified + * downstream {@code Collector}. The {@code ConcurrentMap} produced by the + * Collector is created with the supplied factory function. + * + *

This is a {@link Collector.Characteristics#CONCURRENT concurrent} and + * {@link Collector.Characteristics#UNORDERED unordered} Collector. + * + *

The classification function maps elements to some key type {@code K}. + * The downstream collector operates on elements of type {@code T} and + * produces a result of type {@code D}. The resulting collector produces a + * {@code Map}. + * + *

For example, to compute the set of last names of people in each city, + * where the city names are sorted: + *

{@code
+     *     ConcurrentMap> namesByCity
+     *         = people.stream().collect(groupingBy(Person::getCity, ConcurrentSkipListMap::new,
+     *                                              mapping(Person::getLastName, toSet())));
+     * }
+ * + * + * @param the type of the input elements + * @param the type of the keys + * @param the result type of the downstream reduction + * @param the type of the resulting {@code ConcurrentMap} + * @param classifier a classifier function mapping input elements to keys + * @param downstream a {@code Collector} implementing the downstream reduction + * @param mapFactory a function which, when called, produces a new empty + * {@code ConcurrentMap} of the desired type + * @return a {@code Collector} implementing the cascaded group-by operation + * + * @see #groupingByConcurrent(Function) + * @see #groupingByConcurrent(Function, Collector) + * @see #groupingBy(Function, Supplier, Collector) + */ + public static > + Collector groupingByConcurrent(Function classifier, + Supplier mapFactory, + Collector downstream) { + Supplier downstreamSupplier = downstream.resultSupplier(); + BiFunction downstreamAccumulator = downstream.accumulator(); + BinaryOperator combiner = mapMerger(downstream.combiner()); + if (downstream.characteristics().contains(Collector.Characteristics.CONCURRENT)) { + BiFunction accumulator = (m, t) -> { + K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key"); + downstreamAccumulator.apply(m.computeIfAbsent(key, k -> downstreamSupplier.get()), t); + return m; + }; + return new CollectorImpl<>(mapFactory, accumulator, combiner, CH_CONCURRENT); + } else if (downstream.characteristics().contains(Collector.Characteristics.STRICTLY_MUTATIVE)) { + BiFunction accumulator = (m, t) -> { + K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key"); + D resultContainer = m.computeIfAbsent(key, k -> downstreamSupplier.get()); + synchronized (resultContainer) { + downstreamAccumulator.apply(resultContainer, t); + } + return m; + }; + return new CollectorImpl<>(mapFactory, accumulator, combiner, CH_CONCURRENT); + } else { + BiFunction accumulator = (m, t) -> { + K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key"); + do { + D oldResult = m.computeIfAbsent(key, k -> downstreamSupplier.get()); + if (oldResult == null) { + if (m.putIfAbsent(key, downstreamAccumulator.apply(null, t)) == null) + return m; + } else { + synchronized (oldResult) { + if (m.get(key) != oldResult) + continue; + D newResult = downstreamAccumulator.apply(oldResult, t); + if (oldResult != newResult) + m.put(key, newResult); + return m; + } + } + } while (true); + }; + return new CollectorImpl<>(mapFactory, accumulator, combiner, CH_CONCURRENT); + } + } + + /** + * Returns a {@code Collector} which partitions the input elements according + * to a {@code Predicate}, and organizes them into a + * {@code Map>}. + * + * There are no guarantees on the type, mutability, + * serializability, or thread-safety of the {@code Map} returned. + * + * @param the type of the input elements + * @param predicate a predicate used for classifying input elements + * @return a {@code Collector} implementing the partitioning operation + * + * @see #partitioningBy(Predicate, Collector) + */ + public static + Collector>> partitioningBy(Predicate predicate) { + return partitioningBy(predicate, toList()); + } + + /** + * Returns a {@code Collector} which partitions the input elements according + * to a {@code Predicate}, reduces the values in each partition according to + * another {@code Collector}, and organizes them into a + * {@code Map} whose values are the result of the downstream + * reduction. + * + *

There are no guarantees on the type, mutability, + * serializability, or thread-safety of the {@code Map} returned. + * + * @param the type of the input elements + * @param the result type of the downstream reduction + * @param predicate a predicate used for classifying input elements + * @param downstream a {@code Collector} implementing the downstream + * reduction + * @return a {@code Collector} implementing the cascaded partitioning + * operation + * + * @see #partitioningBy(Predicate) + */ + public static + Collector> partitioningBy(Predicate predicate, + Collector downstream) { + BiFunction downstreamAccumulator = downstream.accumulator(); + BiFunction, T, Map> accumulator = (result, t) -> { + Partition asPartition = ((Partition) result); + if (predicate.test(t)) { + D newResult = downstreamAccumulator.apply(asPartition.forTrue, t); + if (newResult != asPartition.forTrue) + asPartition.forTrue = newResult; + } else { + D newResult = downstreamAccumulator.apply(asPartition.forFalse, t); + if (newResult != asPartition.forFalse) + asPartition.forFalse = newResult; + } + return result; + }; + return new CollectorImpl<>(() -> new Partition<>(downstream.resultSupplier().get(), + downstream.resultSupplier().get()), + accumulator, partitionMerger(downstream.combiner()), CH_STRICT); + } + + /** + * Merge function for two partitions, given a merge function for the + * elements. + */ + private static BinaryOperator> partitionMerger(BinaryOperator op) { + return (m1, m2) -> { + Partition left = (Partition) m1; + Partition right = (Partition) m2; + if (left.forFalse == null) + left.forFalse = right.forFalse; + else if (right.forFalse != null) + left.forFalse = op.apply(left.forFalse, right.forFalse); + if (left.forTrue == null) + left.forTrue = right.forTrue; + else if (right.forTrue != null) + left.forTrue = op.apply(left.forTrue, right.forTrue); + return left; + }; + } + + /** + * Accumulate elements into a {@code Map} whose keys and values are the + * result of applying mapping functions to the input elements. + * If the mapped keys contains duplicates (according to + * {@link Object#equals(Object)}), an {@code IllegalStateException} is + * thrown when the collection operation is performed. If the mapped keys + * may have duplicates, use {@link #toMap(Function, Function, BinaryOperator)} + * instead. + * + * @apiNote + * It is common for either the key or the value to be the input elements. + * In this case, the utility method + * {@link java.util.function.Function#identity()} may be helpful. + * For example, the following produces a {@code Map} mapping + * students to their grade point average: + *

{@code
+     *     Map studentToGPA
+     *         students.stream().collect(toMap(Functions.identity(),
+     *                                         student -> computeGPA(student)));
+     * }
+ * And the following produces a {@code Map} mapping a unique identifier to + * students: + *
{@code
+     *     Map studentIdToStudent
+     *         students.stream().collect(toMap(Student::getId,
+     *                                         Functions.identity());
+     * }
+ * + * @param the type of the input elements + * @param the output type of the key mapping function + * @param the 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 + * + * @see #toMap(Function, Function, BinaryOperator) + * @see #toMap(Function, Function, BinaryOperator, Supplier) + * @see #toConcurrentMap(Function, Function) + */ + public static + Collector> toMap(Function keyMapper, + Function valueMapper) { + return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new); + } + + /** + * Accumulate elements into a {@code Map} whose keys and values are the + * result of applying mapping functions to the input elements. If the mapped + * keys contains duplicates (according to {@link Object#equals(Object)}), + * the value mapping function is applied to each equal element, and the + * results are merged using the provided merging function. + * + * @apiNote + * There are multiple ways to deal with collisions between multiple elements + * mapping to the same key. There are some predefined merging functions, + * such as {@link #throwingMerger()}, {@link #firstWinsMerger()}, and + * {@link #lastWinsMerger()}, that implement common policies, or you can + * implement custom policies easily. For example, if you have a stream + * of {@code Person}, and you want to produce a "phone book" mapping name to + * address, but it is possible that two persons have the same name, you can + * do as follows to gracefully deals with these collisions, and produce a + * {@code Map} mapping names to a concatenated list of addresses: + *
{@code
+     *     Map phoneBook
+     *         people.stream().collect(toMap(Person::getName,
+     *                                       Person::getAddress,
+     *                                       (s, a) -> s + ", " + a));
+     * }
+ * + * @param the type of the input elements + * @param the output type of the key mapping function + * @param the output type of the value mapping function + * @param keyMapper a mapping function to produce keys + * @param valueMapper a mapping function to produce values + * @param mergeFunction a merge function, used to resolve collisions between + * values associated with the same key, as supplied + * to {@link Map#merge(Object, Object, BiFunction)} + * @return a {@code Collector} which collects elements into a {@code Map} + * whose keys are the result of applying a key mapping function to the input + * elements, and whose values are the result of applying a value mapping + * function to all input elements equal to the key and combining them + * using the merge function + * + * @see #toMap(Function, Function) + * @see #toMap(Function, Function, BinaryOperator, Supplier) + * @see #toConcurrentMap(Function, Function, BinaryOperator) + */ + public static + Collector> toMap(Function keyMapper, + Function valueMapper, + BinaryOperator mergeFunction) { + return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new); + } + + /** + * Accumulate elements into a {@code Map} whose keys and values are the + * result of applying mapping functions to the input elements. If the mapped + * keys contains duplicates (according to {@link Object#equals(Object)}), + * the value mapping function is applied to each equal element, and the + * results are merged using the provided merging function. The {@code Map} + * is created by a provided supplier function. + * + * @param the type of the input elements + * @param the output type of the key mapping function + * @param the output type of the value mapping function + * @param the type of the resulting {@code Map} + * @param keyMapper a mapping function to produce keys + * @param valueMapper a mapping function to produce values + * @param mergeFunction a merge function, used to resolve collisions between + * values associated with the same key, as supplied + * to {@link Map#merge(Object, Object, BiFunction)} + * @param mapSupplier a function which returns a new, empty {@code Map} into + * which the results will be inserted + * @return a {@code Collector} which collects elements into a {@code Map} + * whose keys are the result of applying a key mapping function to the input + * elements, and whose values are the result of applying a value mapping + * function to all input elements equal to the key and combining them + * using the merge function + * + * @see #toMap(Function, Function) + * @see #toMap(Function, Function, BinaryOperator) + * @see #toConcurrentMap(Function, Function, BinaryOperator, Supplier) + */ + public static > + Collector toMap(Function keyMapper, + Function valueMapper, + BinaryOperator mergeFunction, + Supplier mapSupplier) { + BiFunction accumulator + = (map, element) -> { + map.merge(keyMapper.apply(element), valueMapper.apply(element), mergeFunction); + return map; + }; + return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_STRICT); + } + + /** + * Accumulate elements into a {@code ConcurrentMap} whose keys and values + * are the result of applying mapping functions to the input elements. + * If the mapped keys contains duplicates (according to + * {@link Object#equals(Object)}), an {@code IllegalStateException} is + * thrown when the collection operation is performed. If the mapped keys + * may have duplicates, use + * {@link #toConcurrentMap(Function, Function, BinaryOperator)} instead. + * + * @apiNote + * It is common for either the key or the value to be the input elements. + * In this case, the utility method + * {@link java.util.function.Function#identity()} may be helpful. + * For example, the following produces a {@code Map} mapping + * students to their grade point average: + *
{@code
+     *     Map studentToGPA
+     *         students.stream().collect(toMap(Functions.identity(),
+     *                                         student -> computeGPA(student)));
+     * }
+ * And the following produces a {@code Map} mapping a unique identifier to + * students: + *
{@code
+     *     Map studentIdToStudent
+     *         students.stream().collect(toConcurrentMap(Student::getId,
+     *                                                   Functions.identity());
+     * }
+ * + *

This is a {@link Collector.Characteristics#CONCURRENT concurrent} and + * {@link Collector.Characteristics#UNORDERED unordered} Collector. + * + * @param the type of the input elements + * @param the output type of the key mapping function + * @param the output type of the value mapping function + * @param keyMapper the mapping function to produce keys + * @param valueMapper the mapping function to produce values + * @return a concurrent {@code Collector} which collects elements into a + * {@code ConcurrentMap} whose keys are the result of applying a key mapping + * function to the input elements, and whose values are the result of + * applying a value mapping function to the input elements + * + * @see #toMap(Function, Function) + * @see #toConcurrentMap(Function, Function, BinaryOperator) + * @see #toConcurrentMap(Function, Function, BinaryOperator, Supplier) + */ + public static + Collector> toConcurrentMap(Function keyMapper, + Function valueMapper) { + return toConcurrentMap(keyMapper, valueMapper, throwingMerger(), ConcurrentHashMap::new); + } + + /** + * Accumulate elements into a {@code ConcurrentMap} whose keys and values + * are the result of applying mapping functions to the input elements. If + * the mapped keys contains duplicates (according to {@link Object#equals(Object)}), + * the value mapping function is applied to each equal element, and the + * results are merged using the provided merging function. + * + * @apiNote + * There are multiple ways to deal with collisions between multiple elements + * mapping to the same key. There are some predefined merging functions, + * such as {@link #throwingMerger()}, {@link #firstWinsMerger()}, and + * {@link #lastWinsMerger()}, that implement common policies, or you can + * implement custom policies easily. For example, if you have a stream + * of {@code Person}, and you want to produce a "phone book" mapping name to + * address, but it is possible that two persons have the same name, you can + * do as follows to gracefully deals with these collisions, and produce a + * {@code Map} mapping names to a concatenated list of addresses: + *

{@code
+     *     Map phoneBook
+     *         people.stream().collect(toConcurrentMap(Person::getName,
+     *                                                 Person::getAddress,
+     *                                                 (s, a) -> s + ", " + a));
+     * }
+ * + *

This is a {@link Collector.Characteristics#CONCURRENT concurrent} and + * {@link Collector.Characteristics#UNORDERED unordered} Collector. + * + * @param the type of the input elements + * @param the output type of the key mapping function + * @param the output type of the value mapping function + * @param keyMapper a mapping function to produce keys + * @param valueMapper a mapping function to produce values + * @param mergeFunction a merge function, used to resolve collisions between + * values associated with the same key, as supplied + * to {@link Map#merge(Object, Object, BiFunction)} + * @return a concurrent {@code Collector} which collects elements into a + * {@code ConcurrentMap} whose keys are the result of applying a key mapping + * function to the input elements, and whose values are the result of + * applying a value mapping function to all input elements equal to the key + * and combining them using the merge function + * + * @see #toConcurrentMap(Function, Function) + * @see #toConcurrentMap(Function, Function, BinaryOperator, Supplier) + * @see #toMap(Function, Function, BinaryOperator) + */ + public static + Collector> toConcurrentMap(Function keyMapper, + Function valueMapper, + BinaryOperator mergeFunction) { + return toConcurrentMap(keyMapper, valueMapper, mergeFunction, ConcurrentHashMap::new); + } + + /** + * Accumulate elements into a {@code ConcurrentMap} whose keys and values + * are the result of applying mapping functions to the input elements. If + * the mapped keys contains duplicates (according to {@link Object#equals(Object)}), + * the value mapping function is applied to each equal element, and the + * results are merged using the provided merging function. The + * {@code ConcurrentMap} is created by a provided supplier function. + * + *

This is a {@link Collector.Characteristics#CONCURRENT concurrent} and + * {@link Collector.Characteristics#UNORDERED unordered} Collector. + * + * @param the type of the input elements + * @param the output type of the key mapping function + * @param the output type of the value mapping function + * @param the type of the resulting {@code ConcurrentMap} + * @param keyMapper a mapping function to produce keys + * @param valueMapper a mapping function to produce values + * @param mergeFunction a merge function, used to resolve collisions between + * values associated with the same key, as supplied + * to {@link Map#merge(Object, Object, BiFunction)} + * @param mapSupplier a function which returns a new, empty {@code Map} into + * which the results will be inserted + * @return a concurrent {@code Collector} which collects elements into a + * {@code ConcurrentMap} whose keys are the result of applying a key mapping + * function to the input elements, and whose values are the result of + * applying a value mapping function to all input elements equal to the key + * and combining them using the merge function + * + * @see #toConcurrentMap(Function, Function) + * @see #toConcurrentMap(Function, Function, BinaryOperator) + * @see #toMap(Function, Function, BinaryOperator, Supplier) + */ + public static > + Collector toConcurrentMap(Function keyMapper, + Function valueMapper, + BinaryOperator mergeFunction, + Supplier mapSupplier) { + BiFunction accumulator = (map, element) -> { + map.merge(keyMapper.apply(element), valueMapper.apply(element), mergeFunction); + return map; + }; + return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_CONCURRENT); + } + + /** + * Returns a {@code Collector} which applies an {@code int}-producing + * mapping function to each input element, and returns summary statistics + * for the resulting values. + * + * @param the type of the input elements + * @param mapper a mapping function to apply to each element + * @return a {@code Collector} implementing the summary-statistics reduction + * + * @see #toDoubleSummaryStatistics(ToDoubleFunction) + * @see #toLongSummaryStatistics(ToLongFunction) + */ + public static + Collector toIntSummaryStatistics(ToIntFunction mapper) { + return new CollectorImpl<>(IntSummaryStatistics::new, + (r, t) -> { r.accept(mapper.applyAsInt(t)); return r; }, + (l, r) -> { l.combine(r); return l; }, CH_STRICT); + } + + /** + * Returns a {@code Collector} which applies an {@code long}-producing + * mapping function to each input element, and returns summary statistics + * for the resulting values. + * + * @param the type of the input elements + * @param mapper the mapping function to apply to each element + * @return a {@code Collector} implementing the summary-statistics reduction + * + * @see #toDoubleSummaryStatistics(ToDoubleFunction) + * @see #toIntSummaryStatistics(ToIntFunction) + */ + public static + Collector toLongSummaryStatistics(ToLongFunction mapper) { + return new CollectorImpl<>(LongSummaryStatistics::new, + (r, t) -> { r.accept(mapper.applyAsLong(t)); return r; }, + (l, r) -> { l.combine(r); return l; }, CH_STRICT); + } + + /** + * Returns a {@code Collector} which applies an {@code double}-producing + * mapping function to each input element, and returns summary statistics + * for the resulting values. + * + * @param the type of the input elements + * @param mapper a mapping function to apply to each element + * @return a {@code Collector} implementing the summary-statistics reduction + * + * @see #toLongSummaryStatistics(ToLongFunction) + * @see #toIntSummaryStatistics(ToIntFunction) + */ + public static + Collector toDoubleSummaryStatistics(ToDoubleFunction mapper) { + return new CollectorImpl<>(DoubleSummaryStatistics::new, + (r, t) -> { r.accept(mapper.applyAsDouble(t)); return r; }, + (l, r) -> { l.combine(r); return l; }, CH_STRICT); + } + + /** + * Implementation class used by partitioningBy. + */ + private static final class Partition + extends AbstractMap + implements Map { + T forTrue; + T forFalse; + + Partition(T forTrue, T forFalse) { + this.forTrue = forTrue; + this.forFalse = forFalse; + } + + @Override + public Set> entrySet() { + return new AbstractSet>() { + @Override + public Iterator> iterator() { + + return new Iterator>() { + int state = 0; + + @Override + public boolean hasNext() { + return state < 2; + } + + @Override + public Map.Entry next() { + if (state >= 2) + throw new NoSuchElementException(); + return (state++ == 0) + ? new SimpleImmutableEntry<>(false, forFalse) + : new SimpleImmutableEntry<>(true, forTrue); + } + }; + } + + @Override + public int size() { + return 2; + } + }; + } + } +} -- GitLab