/* * Copyright 2016 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package java.util.stream; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.DoubleSummaryStatistics; import java.util.HashMap; import java.util.HashSet; import java.util.IntSummaryStatistics; import java.util.LinkedHashMap; import java.util.List; import java.util.LongSummaryStatistics; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.StringJoiner; import java.util.function.BiConsumer; 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; /** * See <a * href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html">the * official Java API doc</a> for details. */ public final class Collectors { public static <T> Collector<T,?,Double> averagingDouble(ToDoubleFunction<? super T> mapper) { // TODO simplify to only collect average if possible return collectingAndThen(summarizingDouble(mapper), DoubleSummaryStatistics::getAverage); } public static <T> Collector<T,?,Double> averagingInt(ToIntFunction<? super T> mapper) { // TODO simplify to only collect average if possible return collectingAndThen(summarizingInt(mapper), IntSummaryStatistics::getAverage); } public static <T> Collector<T,?,Double> averagingLong(ToLongFunction<? super T> mapper) { // TODO simplify to only collect average if possible return collectingAndThen(summarizingLong(mapper), LongSummaryStatistics::getAverage); } public static <T, A, R, RR> Collector<T, A, RR> collectingAndThen( Collector<T, A, R> downstream, Function<R, RR> finisher) { return new CollectorImpl<>( downstream.supplier(), downstream.accumulator(), downstream.combiner(), downstream.finisher().andThen(finisher)); } public static <T> Collector<T,?,Long> counting() { // Using Long::sum here fails in JDT return reducing(0L, item -> 1L, (a, b) -> (Long) a.longValue() + b.longValue()); } public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy( Function<? super T, ? extends K> classifier) { // TODO inline this and avoid the finisher extra work of copying from a map to another map // kept separate for now to unify implementations and reduce testing required return groupingBy(classifier, toList()); } public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy( Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream) { return groupingBy(classifier, HashMap::new, downstream); } public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy( Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream) { return groupingBy0(() -> { // cannot use LinkedHashMap::new because javac cannot infer correct // return type of method reference return new LinkedHashMap<>(); }, classifier, mapFactory, downstream); } private static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy0( Supplier<Map<K, List<T>>> supplier, Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream) { return Collector.of( supplier, (m, o) -> { K k = classifier.apply(o); List<T> l = m.get(k); if (l == null) { l = new ArrayList<>(); m.put(k, l); } l.add(o); }, (m1, m2) -> mergeAll(m1, m2, Collectors::addAll), m -> { M result = mapFactory.get(); for (Map.Entry<K, List<T>> entry : m.entrySet()) { result.put(entry.getKey(), streamAndCollect(downstream, entry.getValue())); } return result; }); } // not supported // public static <T,K> Collector<T,?,ConcurrentMap<K,List<T>>> groupingByConcurrent( // Function<? super T,? extends K> classifier) // public static <T,K,A,D> Collector<T,?,ConcurrentMap<K,D>> groupingByConcurrent( // Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream) // public static <T,K,A,D,M extends ConcurrentMap<K,D>> Collector<T,?,M> groupingByConcurrent( // Function<? super T,? extends K> classifier, Supplier<M> mapFactory, // Collector<? super T,A,D> downstream) public static Collector<CharSequence,?,String> joining() { // specific implementation rather than calling joining("") since we don't need to worry about // appending delimiters between empty strings return Collector.of( StringBuilder::new, StringBuilder::append, StringBuilder::append, StringBuilder::toString ); } public static Collector<CharSequence,?,String> joining(CharSequence delimiter) { return joining(delimiter, "", ""); } public static Collector<CharSequence, ?, String> joining( final CharSequence delimiter, CharSequence prefix, CharSequence suffix) { return Collector.of( () -> new StringJoiner(delimiter, prefix, suffix), StringJoiner::add, StringJoiner::merge, StringJoiner::toString ); } public static <T, U, A, R> Collector<T, ?, R> mapping( final Function<? super T, ? extends U> mapper, final Collector<? super U, A, R> downstream) { return new CollectorImpl<>( downstream.supplier(), (BiConsumer<A, T>) (A a, T t) -> { downstream.accumulator().accept(a, mapper.apply(t)); }, downstream.combiner(), downstream.finisher()); } public static <T> Collector<T,?,Optional<T>> maxBy(Comparator<? super T> comparator) { return reducing(BinaryOperator.maxBy(comparator)); } public static <T> Collector<T,?,Optional<T>> minBy(final Comparator<? super T> comparator) { return reducing(BinaryOperator.minBy(comparator)); } public static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy( Predicate<? super T> predicate) { return partitioningBy(predicate, toList()); } public static <T, D, A> Collector<T, ?, Map<Boolean, D>> partitioningBy( Predicate<? super T> predicate, Collector<? super T, A, D> downstream) { return groupingBy0(partitionSupplier(), predicate::test, HashMap::new, downstream); } private static <T> Supplier<Map<Boolean, List<T>>> partitionSupplier() { return () -> { Map<Boolean, List<T>> partition = new LinkedHashMap<>(); partition.put(false, new ArrayList<>()); partition.put(true, new ArrayList<>()); return partition; }; } public static <T> Collector<T,?,Optional<T>> reducing(BinaryOperator<T> op) { return reducing(Optional.empty(), Optional::of, (a, b) -> { if (!a.isPresent()) { return b; } if (!b.isPresent()) { return a; } return Optional.of(op.apply(a.get(), b.get())); }); } public static <T> Collector<T,?,T> reducing(T identity, BinaryOperator<T> op) { return reducing(identity, Function.identity(), op); } @SuppressWarnings("unchecked") public static <T, U> Collector<T, ?, U> reducing( final U identity, final Function<? super T, ? extends U> mapper, BinaryOperator<U> op) { return Collector.of( () -> new Object[]{identity}, (u, t) -> u[0] = op.apply((U) u[0], mapper.apply(t)), (Object[] u1, Object[] u2) -> { u1[0] = op.apply((U) u1[0], (U) u2[0]); return u1; }, (Object[] a) -> (U) a[0] ); } public static <T> Collector<T, ?, DoubleSummaryStatistics> summarizingDouble( ToDoubleFunction<? super T> mapper) { return Collector.of( DoubleSummaryStatistics::new, (stats, item) -> stats.accept(mapper.applyAsDouble(item)), (t, u) -> { t.combine(u); return t; }, Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH ); } public static <T> Collector<T, ?, IntSummaryStatistics> summarizingInt( ToIntFunction<? super T> mapper) { return Collector.of( IntSummaryStatistics::new, (stats, item) -> stats.accept(mapper.applyAsInt(item)), (t, u) -> { t.combine(u); return t; }, Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH ); } public static <T> Collector<T, ?, LongSummaryStatistics> summarizingLong( ToLongFunction<? super T> mapper) { return Collector.of( LongSummaryStatistics::new, (stats, item) -> stats.accept(mapper.applyAsLong(item)), (t, u) -> { t.combine(u); return t; }, Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH ); } public static <T> Collector<T,?,Double> summingDouble(final ToDoubleFunction<? super T> mapper) { // TODO simplify to only collect sum if possible return collectingAndThen(summarizingDouble(mapper), DoubleSummaryStatistics::getSum); } public static <T> Collector<T,?,Integer> summingInt(ToIntFunction<? super T> mapper) { // TODO simplify to only collect sum if possible return collectingAndThen( summarizingInt(mapper), intSummaryStatistics -> (int) intSummaryStatistics.getSum()); } public static <T> Collector<T,?,Long> summingLong(ToLongFunction<? super T> mapper) { // TODO simplify to only collect sum if possible return collectingAndThen(summarizingLong(mapper), LongSummaryStatistics::getSum); } public static <T, C extends Collection<T>> Collector<T, ?, C> toCollection( final Supplier<C> collectionFactory) { return Collector.of( collectionFactory, Collection::add, // TODO switch to a lambda reference once #9333 is fixed (c1, c2) -> addAll(c1, c2), Collector.Characteristics.IDENTITY_FINISH ); } // not supported // public static <T,K,U> Collector<T,?,ConcurrentMap<K,U>> toConcurrentMap( // Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper) // public static <T,K,U> Collector<T,?,ConcurrentMap<K,U>> toConcurrentMap( // Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, // BinaryOperator<U> mergeFunction) // public static <T,K,U,M extends ConcurrentMap<K,U>> Collector<T,?,M> toConcurrentMap( // Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, // BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) public static <T> Collector<T,?,List<T>> toList() { return toCollection(ArrayList::new); } public static <T, K, U> Collector<T, ?, Map<K, U>> toMap( final Function<? super T, ? extends K> keyMapper, final Function<? super T, ? extends U> valueMapper) { return toMap( keyMapper, valueMapper, (m1, m2) -> { throw new IllegalStateException("Can't assign multiple values to the same key"); }); } public static <T, K, U> Collector<T, ?, Map<K, U>> toMap( Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) { return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new); } public static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap( final Function<? super T, ? extends K> keyMapper, final Function<? super T, ? extends U> valueMapper, final BinaryOperator<U> mergeFunction, final Supplier<M> mapSupplier) { return Collector.of( mapSupplier, (map, item) -> { K key = keyMapper.apply(item); U newValue = valueMapper.apply(item); if (map.containsKey(key)) { map.put(key, mergeFunction.apply(map.get(key), newValue)); } else { map.put(key, newValue); } }, (m1, m2) -> mergeAll(m1, m2, mergeFunction), Collector.Characteristics.IDENTITY_FINISH); } public static <T> Collector<T,?,Set<T>> toSet() { return Collector.<T, HashSet<T>, Set<T>>of( HashSet::new, HashSet::add, // TODO switch to a lambda reference once #9333 is fixed (c1, c2) -> addAll(c1, c2), // this is Function.identity, but Java doesn't like it here to change types. s -> s, Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH ); } private static <T, D, A> D streamAndCollect(Collector<? super T, A, D> downstream, List<T> list) { A a = downstream.supplier().get(); for (T t : list) { downstream.accumulator().accept(a, t); } return downstream.finisher().apply(a); } private static <K, V, M extends Map<K, V>> M mergeAll( M m1, M m2, BinaryOperator<V> mergeFunction) { for (Map.Entry<K, V> entry : m2.entrySet()) { m1.merge(entry.getKey(), entry.getValue(), mergeFunction); } return m1; } private static <T, C extends Collection<T>> C addAll(C collection, Collection<T> items) { collection.addAll(items); return collection; } private Collectors() { } }