--- old/src/share/classes/java/util/stream/Collectors.java 2013-07-05 16:27:34.174022748 -0700 +++ new/src/share/classes/java/util/stream/Collectors.java 2013-07-05 16:27:30.506022813 -0700 @@ -27,6 +27,7 @@ import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -39,14 +40,16 @@ import java.util.List; import java.util.LongSummaryStatistics; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.StringJoiner; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.BinaryOperator; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; @@ -64,20 +67,21 @@ * mutable reduction tasks: * *
{@code
- *     // Accumulate elements into a List
- *     List list = people.collect(Collectors.toList());
+ *     // Accumulate names into a List
+ *     List list = people.stream().map(Person::getName).collect(Collectors.toList());
  *
- *     // Accumulate elements into a TreeSet
- *     List list = people.collect(Collectors.toCollection(TreeSet::new));
+ *     // Accumulate names into a TreeSet
+ *     Set list = people.stream().map(Person::getName).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();
+ *     String joined = things.stream()
+ *                           .map(Object::toString)
+ *                           .collect(Collectors.joining(", "));
  *
  *     // Find highest-paid employee
  *     Employee highestPaid = employees.stream()
- *                                     .collect(Collectors.maxBy(Comparator.comparing(Employee::getSalary)));
+ *                                     .collect(Collectors.maxBy(Comparator.comparing(Employee::getSalary)))
+ *                                     .get();
  *
  *     // Group employees by department
  *     Map> byDept
@@ -85,7 +89,7 @@
  *                    .collect(Collectors.groupingBy(Employee::getDepartment));
  *
  *     // Find highest-paid employee by department
- *     Map highestPaidByDept
+ *     Map> highestPaidByDept
  *         = employees.stream()
  *                    .collect(Collectors.groupingBy(Employee::getDepartment,
  *                                                   Collectors.maxBy(Comparator.comparing(Employee::getSalary))));
@@ -93,7 +97,7 @@
  *     // Partition students into passing and failing
  *     Map> passingFailing =
  *         students.stream()
- *                 .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD);
+ *                 .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
  *
  * }
* @@ -103,15 +107,19 @@ */ public final class Collectors { - private static final Set CH_CONCURRENT + static final Set CH_CONCURRENT_ID + = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.CONCURRENT, + Collector.Characteristics.UNORDERED, + Collector.Characteristics.IDENTITY_FINISH)); + static final Set CH_CONCURRENT_NOID = 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)); + static final Set CH_ID + = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH)); + static final Set CH_UNORDERED_ID + = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.UNORDERED, + Collector.Characteristics.IDENTITY_FINISH)); + static final Set CH_NOID = Collections.emptySet(); private Collectors() { } @@ -124,88 +132,64 @@ * * @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() { + private 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; + static class CollectorImpl implements Collector { + private final Supplier supplier; + private final BiConsumer accumulator; + private final BinaryOperator combiner; + private final Function finisher; private final Set characteristics; - CollectorImpl(Supplier resultSupplier, - BiFunction accumulator, - BinaryOperator combiner, + CollectorImpl(Supplier supplier, + BiConsumer accumulator, + BinaryOperator combiner, + Function finisher, Set characteristics) { - this.resultSupplier = resultSupplier; + this.supplier = supplier; this.accumulator = accumulator; this.combiner = combiner; + this.finisher = finisher; this.characteristics = characteristics; } - CollectorImpl(Supplier resultSupplier, - BiFunction accumulator, - BinaryOperator combiner) { - this(resultSupplier, accumulator, combiner, Collections.emptySet()); + CollectorImpl(Supplier supplier, + BiConsumer accumulator, + BinaryOperator combiner, + Set characteristics) { + this(supplier, accumulator, combiner, i -> (R) i, characteristics); } @Override - public BiFunction accumulator() { + public BiConsumer accumulator() { return accumulator; } @Override - public Supplier resultSupplier() { - return resultSupplier; + public Supplier supplier() { + return supplier; } @Override - public BinaryOperator combiner() { + public BinaryOperator combiner() { return combiner; } @Override + public Function finisher() { + return finisher; + } + + @Override public Set characteristics() { return characteristics; } @@ -224,11 +208,10 @@ * {@code Collection}, in encounter order */ public static > - Collector toCollection(Supplier collectionFactory) { - return new CollectorImpl<>(collectionFactory, - (r, t) -> { r.add(t); return r; }, + Collector toCollection(Supplier collectionFactory) { + return new CollectorImpl<>(collectionFactory, Collection::add, (r1, r2) -> { r1.addAll(r2); return r1; }, - CH_STRICT); + CH_ID); } /** @@ -241,36 +224,10 @@ * {@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); + Collector> toList() { + return new CollectorImpl<>((Supplier>) ArrayList::new, List::add, + (left, right) -> { left.addAll(right); return left; }, + CH_ID); } /** @@ -286,44 +243,58 @@ * {@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); + Collector> toSet() { + return new CollectorImpl<>((Supplier>) HashSet::new, Set::add, + (left, right) -> { left.addAll(right); return left; }, + CH_UNORDERED_ID); } /** * Returns a {@code Collector} that concatenates the input elements into a - * new {@link StringBuilder}. + * {@code String}, in encounter order. * - * @return a {@code Collector} which collects String elements into a - * {@code StringBuilder}, in encounter order + * @return a {@code Collector} that concatenates the input elements into a + * {@code String}, 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); + public static Collector joining() { + return new CollectorImpl( + StringBuilder::new, StringBuilder::append, + (r1, r2) -> { r1.append(r2); return r1; }, + StringBuilder::toString, CH_NOID); } /** - * Returns a {@code Collector} that concatenates the input elements into a - * new {@link StringJoiner}, using the specified delimiter. + * Returns a {@code Collector} that concatenates the input elements, + * separated by the specified delimiter, in encounter order. * * @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 + * @return A {@code Collector} which concatenates CharSequence elements, + * separated by the specified delimiter, 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); + public static Collector joining(CharSequence delimiter) { + return joining(delimiter, "", ""); + } + + /** + * Returns a {@code Collector} that concatenates the input elements, + * separated by the specified delimiter, with the specified prefix and + * suffix, in encounter order. + * + * @param delimiter the delimiter to be used between each element + * @param prefix the sequence of characters to be used at the beginning + * of the joined result + * @param suffix the sequence of characters to be used at the end + * of the joined result + * @return A {@code Collector} which concatenates CharSequence elements, + * separated by the specified delimiter, in encounter order + */ + public static Collector joining(CharSequence delimiter, + CharSequence prefix, + CharSequence suffix) { + return new CollectorImpl<>( + () -> new StringJoiner(delimiter, prefix, suffix), + StringJoiner::add, StringJoiner::merge, + StringJoiner::toString, CH_NOID); } /** @@ -348,12 +319,13 @@ } /** - * Adapts a {@code Collector} to a {@code Collector} by applying - * a mapping function to each input element before accumulation. + * Adapts a {@code Collector} accepting elements of type {@code U} to one + * accepting elements of type {@code T} 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 + * multi-level reduction, such as downstream of a {@code groupingBy} or * {@code partitioningBy}. For example, given a stream of * {@code Person}, to accumulate the set of last names in each city: *
{@code
@@ -364,23 +336,27 @@
      *
      * @param  the type of the input elements
      * @param  type of elements accepted by downstream collector
+     * @param  intermediate accumulation type of the 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());
+    public static 
+    Collector mapping(Function mapper,
+                               Collector downstream) {
+        BiConsumer downstreamAccumulator = downstream.accumulator();
+        return new CollectorImpl<>(downstream.supplier(),
+                                   (r, t) -> downstreamAccumulator.accept(r, mapper.apply(t)),
+                                   downstream.combiner(), downstream.finisher(),
+                                   downstream.characteristics());
     }
 
     /**
-     * Returns a {@code Collector} that counts the number of input
-     * elements.
+     * Returns a {@code Collector} accepting elements of type {@code T} that
+     * counts the number of input elements.  If no elements are present, the
+     * result is 0.
      *
      * @implSpec
      * This produces a result equivalent to:
@@ -391,14 +367,14 @@
      * @param  the type of the input elements
      * @return a {@code Collector} that counts the input elements
      */
-    public static  Collector
+    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}.
+     * Returns a {@code Collector} that produces the minimal element according
+     * to a given {@code Comparator}, described as an {@code Optional}.
      *
      * @implSpec
      * This produces a result equivalent to:
@@ -410,14 +386,14 @@
      * @param comparator a {@code Comparator} for comparing elements
      * @return a {@code Collector} that produces the minimal value
      */
-    public static  Collector
+    public static  Collector>
     minBy(Comparator comparator) {
         return reducing(BinaryOperator.minBy(comparator));
     }
 
     /**
-     * Returns a {@code Collector} that produces the maximal element
-     * according to a given {@code Comparator}.
+     * Returns a {@code Collector} that produces the maximal element according
+     * to a given {@code Comparator}, described as an {@code Optional}.
      *
      * @implSpec
      * This produces a result equivalent to:
@@ -429,39 +405,143 @@
      * @param comparator a {@code Comparator} for comparing elements
      * @return a {@code Collector} that produces the maximal value
      */
-    public static  Collector
+    public static  Collector>
     maxBy(Comparator comparator) {
         return reducing(BinaryOperator.maxBy(comparator));
     }
 
     /**
-     * Returns a {@code Collector} that produces the sum of a
-     * long-valued function applied to the input element.
+     * Returns a {@code Collector} that produces the sum of a integer-valued
+     * function applied to the input elements.  If no elements are present,
+     * the result is 0.
      *
-     * @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 + summingInt(ToIntFunction mapper) { + return new CollectorImpl( + () -> new int[1], + (a, t) -> { a[0] += mapper.applyAsInt(t); }, + (a, b) -> { a[0] += b[0]; return a; }, + a -> a[0], CH_NOID); + } + + /** + * Returns a {@code Collector} that produces the sum of a long-valued + * function applied to the input elements. If no elements are present, + * the result is 0. + * + * @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 + summingLong(ToLongFunction mapper) { + return new CollectorImpl( + () -> new long[1], + (a, t) -> { a[0] += mapper.applyAsLong(t); }, + (a, b) -> { a[0] += b[0]; return a; }, + a -> a[0], CH_NOID); + } + + /** + * Returns a {@code Collector} that produces the sum of a double-valued + * function applied to the input elements. If no elements are present, + * the result is 0. + * + *

The sum returned can vary depending upon the order in which + * values are recorded, due to accumulated rounding error in + * addition of values of differing magnitudes. Values sorted by increasing + * absolute magnitude tend to yield more accurate results. If any recorded + * value is a {@code NaN} or the sum is at any point a {@code NaN} then the + * sum will be {@code NaN}. + * + * @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 + summingDouble(ToDoubleFunction mapper) { + return new CollectorImpl( + () -> new double[1], + (a, t) -> { a[0] += mapper.applyAsDouble(t); }, + (a, b) -> { a[0] += b[0]; return a; }, + a -> a[0], CH_NOID); + } + + /** + * Returns a {@code Collector} that produces the average of an integer-valued + * function applied to the input elements. If no elements are present, + * the result is 0. * * @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); + public static Collector + averagingInt(ToIntFunction mapper) { + return new CollectorImpl( + () -> new long[2], + (a, t) -> { a[0] += mapper.applyAsInt(t); a[1]++; }, + (a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; }, + a -> (a[1] == 0) ? 0.0d : (double) a[0] / a[1], CH_NOID); } /** - * Returns a {@code Collector} which performs a reduction of its - * input elements under a specified {@code BinaryOperator}. + * Returns a {@code Collector} that produces the average of a long-valued + * function applied to the input elements. If no elements are present, + * the result is 0. + * + * @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 + averagingLong(ToLongFunction mapper) { + return new CollectorImpl( + () -> new long[2], + (a, t) -> { a[0] += mapper.applyAsLong(t); a[1]++; }, + (a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; }, + a -> (a[1] == 0) ? 0.0d : (double) a[0] / a[1], CH_NOID); + } + + /** + * Returns a {@code Collector} that produces the average of a double-valued + * function applied to the input elements. If no elements are present, + * the result is 0. + * + *

The average returned can vary depending upon the order in which + * values are recorded, due to accumulated rounding error in + * addition of values of differing magnitudes. Values sorted by increasing + * absolute magnitude tend to yield more accurate results. If any recorded + * value is a {@code NaN} or the sum is at any point a {@code NaN} then the + * average will be {@code NaN}. + * + * @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 + averagingDouble(ToDoubleFunction mapper) { + return new CollectorImpl( + () -> new double[2], + (a, t) -> { a[0] += mapper.applyAsDouble(t); a[1]++; }, + (a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; }, + a -> (a[1] == 0) ? 0.0d : a[0] / a[1], CH_NOID); + } + + /** + * Returns a {@code Collector} which performs a reduction of its + * input elements under a specified {@code BinaryOperator} using the + * provided identity. * * @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. + * use {@link Stream#reduce(Object, BinaryOperator)}} instead. * * @param element type for the input and output of the reduction * @param identity the identity value for the reduction (also, the value @@ -472,14 +552,25 @@ * @see #reducing(BinaryOperator) * @see #reducing(Object, Function, BinaryOperator) */ - public static Collector + public static Collector reducing(T identity, BinaryOperator op) { - return new CollectorImpl<>(() -> identity, (r, t) -> (r == null ? t : op.apply(r, t)), op); + return new CollectorImpl<>( + boxSupplier(identity), + (a, t) -> { a[0] = op.apply(a[0], t); }, + (a, b) -> { a[0] = op.apply(a[0], b[0]); return a; }, + a -> a[0], + CH_NOID); + } + + @SuppressWarnings("unchecked") + private static Supplier boxSupplier(T identity) { + return () -> (T[]) new Object[] { identity }; } /** - * Returns a {@code Collector} which performs a reduction of its - * input elements under a specified {@code BinaryOperator}. + * Returns a {@code Collector} which performs a reduction of its + * input elements under a specified {@code BinaryOperator}. The result + * is described as an {@code Optional}. * * @apiNote * The {@code reducing()} collectors are most useful when used in a @@ -491,15 +582,8 @@ * person in each city: *

{@code
      *     Comparator byHeight = Comparator.comparing(Person::getHeight);
-     *     BinaryOperator tallerOf = BinaryOperator.greaterOf(byHeight);
      *     Map tallestByCity
-     *         = people.stream().collect(groupingBy(Person::getCity, reducing(tallerOf)));
-     * }
- * - * @implSpec - * The default implementation is equivalent to: - *
{@code
-     *     reducing(null, op);
+     *         = people.stream().collect(groupingBy(Person::getCity, reducing(BinaryOperator.maxBy(byHeight))));
      * }
* * @param element type for the input and output of the reduction @@ -509,13 +593,32 @@ * @see #reducing(Object, BinaryOperator) * @see #reducing(Object, Function, BinaryOperator) */ - public static Collector + public static Collector> reducing(BinaryOperator op) { - return reducing(null, op); + class OptionalBox implements Consumer { + T value = null; + boolean present = false; + + @Override + public void accept(T t) { + if (present) { + value = op.apply(value, t); + } + else { + value = t; + present = true; + } + } + } + + return new CollectorImpl>( + OptionalBox::new, OptionalBox::accept, + (a, b) -> { if (b.present) a.accept(b.value); return a; }, + a -> Optional.ofNullable(a.value), CH_NOID); } /** - * Returns a {@code Collector} which performs a reduction of its + * 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 @@ -524,17 +627,17 @@ * @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. + * {@code partitioningBy}. To perform a simple map-reduce on a stream, + * use {@link Stream#map(Function)} and {@link Stream#reduce(Object, BinaryOperator)} + * instead. * *

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

{@code
      *     Comparator byLength = Comparator.comparing(String::length);
-     *     BinaryOperator longerOf = BinaryOperator.greaterOf(byLength);
      *     Map longestLastNameByCity
      *         = people.stream().collect(groupingBy(Person::getCity,
-     *                                              reducing(Person::getLastName, longerOf)));
+     *                                              reducing(Person::getLastName, BinaryOperator.maxBy(byLength))));
      * }
* * @param the type of the input elements @@ -549,18 +652,20 @@ * @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); + Collector reducing(U identity, + Function mapper, + BinaryOperator op) { + return new CollectorImpl<>( + boxSupplier(identity), + (a, t) -> { a[0] = op.apply(a[0], mapper.apply(t)); }, + (a, b) -> { a[0] = op.apply(a[0], b[0]); return a; }, + a -> a[0], CH_NOID); } /** * Returns a {@code Collector} implementing a "group by" operation on * input elements of type {@code T}, grouping elements according to a - * classification function. + * classification function, and returning the results in a {@code Map}. * *

The classification function maps elements to some key type {@code K}. * The collector produces a {@code Map>} whose keys are the @@ -586,9 +691,9 @@ * @see #groupingBy(Function, Supplier, Collector) * @see #groupingByConcurrent(Function) */ - public static - Collector>> groupingBy(Function classifier) { - return groupingBy(classifier, HashMap::new, toList()); + public static Collector>> + groupingBy(Function classifier) { + return groupingBy(classifier, toList()); } /** @@ -615,6 +720,7 @@ * * @param the type of the input elements * @param the type of the keys + * @param the intermediate accumulation type of the downstream collector * @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 @@ -624,9 +730,9 @@ * @see #groupingBy(Function, Supplier, Collector) * @see #groupingByConcurrent(Function, Collector) */ - public static - Collector> groupingBy(Function classifier, - Collector downstream) { + public static + Collector> groupingBy(Function classifier, + Collector downstream) { return groupingBy(classifier, HashMap::new, downstream); } @@ -653,6 +759,7 @@ * * @param the type of the input elements * @param the type of the keys + * @param the intermediate accumulation type of the downstream collector * @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 @@ -665,25 +772,39 @@ * @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) -> { + public static > + Collector groupingBy(Function classifier, + Supplier mapFactory, + Collector downstream) { + Supplier downstreamSupplier = downstream.supplier(); + BiConsumer downstreamAccumulator = downstream.accumulator(); + BiConsumer, T> 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; + A container = m.computeIfAbsent(key, k -> downstreamSupplier.get()); + downstreamAccumulator.accept(container, t); }; - return new CollectorImpl<>(mapFactory, accumulator, mapMerger(downstream.combiner()), CH_STRICT); + BinaryOperator> merger = Collectors.>mapMerger(downstream.combiner()); + @SuppressWarnings("unchecked") + Supplier> mangledFactory = (Supplier>) mapFactory; + + if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { + return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID); + } + else { + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); + Function, M> finisher = intermediate -> { + intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); + @SuppressWarnings("unchecked") + M castResult = (M) intermediate; + return castResult; + }; + return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID); + } } /** - * Returns a {@code Collector} implementing a concurrent "group by" + * Returns a concurrent {@code Collector} implementing a "group by" * operation on input elements of type {@code T}, grouping elements * according to a classification function. * @@ -716,12 +837,13 @@ * @see #groupingByConcurrent(Function, Supplier, Collector) */ public static - Collector>> groupingByConcurrent(Function classifier) { + Collector>> + groupingByConcurrent(Function classifier) { return groupingByConcurrent(classifier, ConcurrentHashMap::new, toList()); } /** - * Returns a {@code Collector} implementing a concurrent cascaded "group by" + * 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 @@ -739,12 +861,13 @@ * where the city names are sorted: *

{@code
      *     ConcurrentMap> namesByCity
-     *         = people.stream().collect(groupingByConcurrent(Person::getCity, TreeMap::new,
+     *         = people.stream().collect(groupingByConcurrent(Person::getCity, ConcurrentSkipListMap::new,
      *                                                        mapping(Person::getLastName, toSet())));
      * }
* * @param the type of the input elements * @param the type of the keys + * @param the intermediate accumulation type of the downstream collector * @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 @@ -754,9 +877,9 @@ * @see #groupingByConcurrent(Function) * @see #groupingByConcurrent(Function, Supplier, Collector) */ - public static - Collector> groupingByConcurrent(Function classifier, - Collector downstream) { + public static + Collector> groupingByConcurrent(Function classifier, + Collector downstream) { return groupingByConcurrent(classifier, ConcurrentHashMap::new, downstream); } @@ -787,6 +910,7 @@ * * @param the type of the input elements * @param the type of the keys + * @param the intermediate accumulation type of the downstream collector * @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 @@ -799,51 +923,46 @@ * @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()); + public static > + Collector groupingByConcurrent(Function classifier, + Supplier mapFactory, + Collector downstream) { + Supplier downstreamSupplier = downstream.supplier(); + BiConsumer downstreamAccumulator = downstream.accumulator(); + BinaryOperator> merger = Collectors.>mapMerger(downstream.combiner()); + @SuppressWarnings("unchecked") + Supplier> mangledFactory = (Supplier>) mapFactory; + BiConsumer, T> accumulator; if (downstream.characteristics().contains(Collector.Characteristics.CONCURRENT)) { - BiFunction accumulator = (m, t) -> { + 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; + A resultContainer = m.computeIfAbsent(key, k -> downstreamSupplier.get()); + downstreamAccumulator.accept(resultContainer, t); }; - return new CollectorImpl<>(mapFactory, accumulator, combiner, CH_CONCURRENT); - } else if (downstream.characteristics().contains(Collector.Characteristics.STRICTLY_MUTATIVE)) { - BiFunction accumulator = (m, t) -> { + } + else { + 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()); + A resultContainer = m.computeIfAbsent(key, k -> downstreamSupplier.get()); synchronized (resultContainer) { - downstreamAccumulator.apply(resultContainer, t); + downstreamAccumulator.accept(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); + } + + if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { + return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_CONCURRENT_ID); + } + else { + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); + Function, M> finisher = intermediate -> { + intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); + @SuppressWarnings("unchecked") + M castResult = (M) intermediate; + return castResult; }; - return new CollectorImpl<>(mapFactory, accumulator, combiner, CH_CONCURRENT); + return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_CONCURRENT_NOID); } } @@ -862,7 +981,7 @@ * @see #partitioningBy(Predicate, Collector) */ public static - Collector>> partitioningBy(Predicate predicate) { + Collector>> partitioningBy(Predicate predicate) { return partitioningBy(predicate, toList()); } @@ -877,6 +996,7 @@ * serializability, or thread-safety of the {@code Map} returned. * * @param the type of the input elements + * @param the intermediate accumulation type of the downstream collector * @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 @@ -886,52 +1006,43 @@ * * @see #partitioningBy(Predicate) */ - public static - Collector> partitioningBy(Predicate predicate, - Collector downstream) { - BiFunction downstreamAccumulator = downstream.accumulator(); - BiFunction, T, Map> accumulator = (result, t) -> { + public static + Collector> partitioningBy(Predicate predicate, + Collector downstream) { + @SuppressWarnings("unchecked") + BiConsumer downstreamAccumulator = (BiConsumer) downstream.accumulator(); + BiConsumer, T> 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; + downstreamAccumulator.accept(predicate.test(t) ? asPartition.forTrue : asPartition.forFalse, t); }; - 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; + BinaryOperator op = downstream.combiner(); + BinaryOperator> merger = (m1, m2) -> { + Partition left = (Partition) m1; + Partition right = (Partition) m2; + return new Partition<>(op.apply(left.forTrue, right.forTrue), + op.apply(left.forFalse, right.forFalse)); }; + Supplier> supplier = () -> new Partition<>(downstream.supplier().get(), + downstream.supplier().get()); + if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { + return new CollectorImpl<>(supplier, accumulator, merger, CH_ID); + } + else { + Function, Map> finisher = (Map par) -> { + Partition asAPartition = (Partition) par; + return new Partition<>(downstream.finisher().apply(asAPartition.forTrue), + downstream.finisher().apply(asAPartition.forFalse)); + }; + return new CollectorImpl<>(supplier, accumulator, merger, finisher, CH_NOID); + } } /** - * 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 + * Returns a {@code Collector} that accumulate elements into a + * {@code Map} whose keys and values are the result of applying the provided + * 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)} @@ -970,24 +1081,26 @@ * @see #toConcurrentMap(Function, Function) */ public static - Collector> toMap(Function keyMapper, - Function valueMapper) { + 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 + * Returns a {@code Collector} that accumulate elements into a + * {@code Map} whose keys and values are the result of applying the provided + * 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 + * mapping to the same key. The other forms of {@code toMap} simply use + * a merge function that throws unconditionally, but you can easily write + * more flexible merge policies. 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 @@ -1018,15 +1131,18 @@ * @see #toConcurrentMap(Function, Function, BinaryOperator) */ public static - Collector> toMap(Function keyMapper, - Function valueMapper, - BinaryOperator mergeFunction) { + 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 + * Returns a {@code Collector} that accumulate elements into a + * {@code Map} whose keys and values are the result of applying the provided + * 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} @@ -1054,22 +1170,22 @@ * @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); + Collector toMap(Function keyMapper, + Function valueMapper, + BinaryOperator mergeFunction, + Supplier mapSupplier) { + BiConsumer accumulator + = (map, element) -> map.merge(keyMapper.apply(element), + valueMapper.apply(element), mergeFunction); + return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID); } /** - * 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 + * Returns a {@code Collector} that accumulate elements into a + * {@code ConcurrentMap} whose keys and values are the result of applying + * the provided 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 @@ -1112,24 +1228,25 @@ * @see #toConcurrentMap(Function, Function, BinaryOperator, Supplier) */ public static - Collector> toConcurrentMap(Function keyMapper, - Function valueMapper) { + 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)}), + * Returns a {@code Collector} that accumulate elements into a + * {@code ConcurrentMap} whose keys and values are the result of applying + * the provided 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 + * mapping to the same key. The other forms of {@code toConcurrentMap} simply use + * a merge function that throws unconditionally, but you can easily write + * more flexible merge policies. 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 @@ -1163,16 +1280,19 @@ * @see #toMap(Function, Function, BinaryOperator) */ public static - Collector> toConcurrentMap(Function keyMapper, - Function valueMapper, - BinaryOperator mergeFunction) { + 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)}), + * Returns a {@code Collector} that accumulate elements into a + * {@code ConcurrentMap} whose keys and values are the result of applying + * the provided 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. @@ -1202,15 +1322,14 @@ * @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); + Collector toConcurrentMap(Function keyMapper, + Function valueMapper, + BinaryOperator mergeFunction, + Supplier mapSupplier) { + BiConsumer accumulator + = (map, element) -> map.merge(keyMapper.apply(element), + valueMapper.apply(element), mergeFunction); + return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_CONCURRENT_ID); } /** @@ -1222,14 +1341,15 @@ * @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) + * @see #summarizingDouble(ToDoubleFunction) + * @see #summarizingLong(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); + Collector summarizingInt(ToIntFunction mapper) { + return new CollectorImpl( + IntSummaryStatistics::new, + (r, t) -> r.accept(mapper.applyAsInt(t)), + (l, r) -> { l.combine(r); return l; }, CH_ID); } /** @@ -1241,14 +1361,15 @@ * @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) + * @see #summarizingDouble(ToDoubleFunction) + * @see #summarizingInt(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); + Collector summarizingLong(ToLongFunction mapper) { + return new CollectorImpl( + LongSummaryStatistics::new, + (r, t) -> r.accept(mapper.applyAsLong(t)), + (l, r) -> { l.combine(r); return l; }, CH_ID); } /** @@ -1260,14 +1381,15 @@ * @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) + * @see #summarizingLong(ToLongFunction) + * @see #summarizingInt(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); + Collector summarizingDouble(ToDoubleFunction mapper) { + return new CollectorImpl( + DoubleSummaryStatistics::new, + (r, t) -> r.accept(mapper.applyAsDouble(t)), + (l, r) -> { l.combine(r); return l; }, CH_ID); } /** @@ -1276,8 +1398,8 @@ private static final class Partition extends AbstractMap implements Map { - T forTrue; - T forFalse; + final T forTrue; + final T forFalse; Partition(T forTrue, T forFalse) { this.forTrue = forTrue; @@ -1289,24 +1411,9 @@ 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); - } - }; + Map.Entry falseEntry = new SimpleImmutableEntry<>(false, forFalse); + Map.Entry trueEntry = new SimpleImmutableEntry<>(true, forTrue); + return Arrays.asList(falseEntry, trueEntry).iterator(); } @Override