# HG changeset patch # User shinyafox # Date 1450016435 -3600 # Node ID ababd79c3b2b40e624f5832d0b66ff64f5d977e8 # Parent b721350c05c0f20a5422bfc71d36d520e0dff2dc 8144675: Add a filtering collector Reviewed-by: psandoz, smarks diff -r b721350c05c0 -r ababd79c3b2b jdk/src/java.base/share/classes/java/util/stream/Collectors.java --- a/jdk/src/java.base/share/classes/java/util/stream/Collectors.java Sun Dec 13 15:10:13 2015 +0100 +++ b/jdk/src/java.base/share/classes/java/util/stream/Collectors.java Sun Dec 13 15:20:35 2015 +0100 @@ -434,7 +434,7 @@ * stream returned by mapper * @return a collector which applies the mapping function to the input * elements and provides the flat mapped results to the downstream collector - * @since 1.9 + * @since 9 */ public static Collector flatMapping(Function> mapper, @@ -452,6 +452,53 @@ } /** + * Adapts a {@code Collector} to one accepting elements of the same type + * {@code T} by applying the predicate to each input element and only + * accumulating if the predicate returns {@code true}. + * + * @apiNote + * The {@code filtering()} collectors are most useful when used in a + * multi-level reduction, such as downstream of a {@code groupingBy} or + * {@code partitioningBy}. For example, given a stream of + * {@code Employee}, to accumulate the employees in each department that have a + * salary above a certain threshold: + *
{@code
+     *     Map> wellPaidEmployeesByDepartment
+     *         = employees.stream().collect(groupingBy(Employee::getDepartment,
+     *                                              filtering(e -> e.getSalary() > 2000, toSet())));
+     * }
+ * A filtering collector differs from a stream's {@code filter()} operation. + * In this example, suppose there are no employees whose salary is above the + * threshold in some department. Using a filtering collector as shown above + * would result in a mapping from that department to an empty {@code Set}. + * If a stream {@code filter()} operation were done instead, there would be + * no mapping for that department at all. + * + * @param the type of the input elements + * @param intermediate accumulation type of the downstream collector + * @param result type of collector + * @param predicate a predicate to be applied to the input elements + * @param downstream a collector which will accept values that match the + * predicate + * @return a collector which applies the predicate to the input elements + * and provides matching elements to the downstream collector + * @since 9 + */ + public static + Collector filtering(Predicate predicate, + Collector downstream) { + BiConsumer downstreamAccumulator = downstream.accumulator(); + return new CollectorImpl<>(downstream.supplier(), + (r, t) -> { + if (predicate.test(t)) { + downstreamAccumulator.accept(r, t); + } + }, + downstream.combiner(), downstream.finisher(), + downstream.characteristics()); + } + + /** * Adapts a {@code Collector} to perform an additional finishing * transformation. For example, one could adapt the {@link #toList()} * collector to always produce an immutable list with: diff -r b721350c05c0 -r ababd79c3b2b jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/CollectorsTest.java --- a/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/CollectorsTest.java Sun Dec 13 15:10:13 2015 +0100 +++ b/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/CollectorsTest.java Sun Dec 13 15:20:35 2015 +0100 @@ -56,6 +56,7 @@ import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.flatMapping; +import static java.util.stream.Collectors.filtering; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.groupingByConcurrent; import static java.util.stream.Collectors.mapping; @@ -72,7 +73,7 @@ /* * @test - * @bug 8071600 + * @bug 8071600 8144675 * @summary Test for collectors. */ public class CollectorsTest extends OpTestCase { @@ -118,6 +119,23 @@ } } + static class FilteringAssertion extends CollectorAssertion { + private final Predicate filter; + private final CollectorAssertion downstream; + + public FilteringAssertion(Predicate filter, CollectorAssertion downstream) { + this.filter = filter; + this.downstream = downstream; + } + + @Override + void assertValue(R value, Supplier> source, boolean ordered) throws ReflectiveOperationException { + downstream.assertValue(value, + () -> source.get().filter(filter), + ordered); + } + } + static class GroupingByAssertion> extends CollectorAssertion { private final Class clazz; private final Function classifier; @@ -551,6 +569,36 @@ } @Test(dataProvider = "StreamTestData", dataProviderClass = StreamTestDataProvider.class) + public void testGroupingByWithFiltering(String name, TestData.OfRef data) throws ReflectiveOperationException { + Function classifier = i -> i % 3; + Predicate filteringByMod2 = i -> i % 2 == 0; + Predicate filteringByUnder100 = i -> i % 2 < 100; + Predicate filteringByTrue = i -> true; + Predicate filteringByFalse = i -> false; + + exerciseMapCollection(data, + groupingBy(classifier, filtering(filteringByMod2, toList())), + new GroupingByAssertion<>(classifier, HashMap.class, + new FilteringAssertion<>(filteringByMod2, + new ToListAssertion<>()))); + exerciseMapCollection(data, + groupingBy(classifier, filtering(filteringByUnder100, toList())), + new GroupingByAssertion<>(classifier, HashMap.class, + new FilteringAssertion<>(filteringByUnder100, + new ToListAssertion<>()))); + exerciseMapCollection(data, + groupingBy(classifier, filtering(filteringByTrue, toList())), + new GroupingByAssertion<>(classifier, HashMap.class, + new FilteringAssertion<>(filteringByTrue, + new ToListAssertion<>()))); + exerciseMapCollection(data, + groupingBy(classifier, filtering(filteringByFalse, toList())), + new GroupingByAssertion<>(classifier, HashMap.class, + new FilteringAssertion<>(filteringByFalse, + new ToListAssertion<>()))); + } + + @Test(dataProvider = "StreamTestData", dataProviderClass = StreamTestDataProvider.class) public void testTwoLevelGroupingBy(String name, TestData.OfRef data) throws ReflectiveOperationException { Function classifier = i -> i % 6; Function classifier2 = i -> i % 23;