上一篇介绍了Stream提供的接口,大部分接口在其它语言中都有对应概念,不过Collector这个概念是自己之前未曾接触过的,因此这里再来看看Java 8里的Collector是如何使用的。

Collector接口定义

Collector接口定义并不复杂,

Collector

其主要由supplier、accumulator、cimbiner、finisher构成。Java 8自带了哪些Collector实现,在Collectors类中可以找寻到。

Collectors

Collectors代码示例

与上一篇一样,一一试用Collectors提供的各函数。

  • averagingInt/averagingLong/averagingDouble,计算平均值
Stream.of(1, 2, 3).collect(Collectors.averagingInt(x -> x))
  • counting,统计数目
Stream.of(1, 1, 2, 3, 5).collect(Collectors.counting());
  • maxBy,获取最大元素
Stream.of(1, 2, 3, 4, 5).collect(Collectors.maxBy((x, y) -> x - y)).ifPresent(System.out::println);
  • minBy,获取最小元素
Stream.of(1, 2, 3, 4, 5).collect(Collectors.minBy((x, y) -> x - y)).ifPresent(System.out::println);
  • summingInt/summingLong/summingDouble
Stream.of(1, 2, 3, 4, 5).collect(Collectors.summingInt(x -> x));
Stream.of(1, 2, 3, 4, 5).collect(Collectors.summingLong(x -> x));
Stream.of(1, 2, 3, 4, 5).collect(Collectors.summingDouble(x -> x));
  • summarizingInt/summarizingLong/summarizingDouble
IntSummaryStatistics statistics = Stream.of(1, 2, 3).collect(Collectors.summarizingInt(x -> x));
System.out.println(statistics);
  • joining,拼接Stream中元素
Stream<String> stream = Stream.of("hello", "world");
stream.collect(Collectors.joining();

Stream<String> stream = Stream.of("hello", "world");
stream.collect(Collectors.joining(", "));

Stream<String> stream = Stream.of("hello", "world");
stream.collect(Collectors.joining(", ", "prefix, ", ", suffix"));
  • toList,将Stream转化成List,实际类型是ArrayList
Stream.of(1, 2, 3, 4, 5).collect(Collectors.toList());
  • toSet,将Stream转化成Set,实际类型是HashSet
Stream.of(1, 2, 3, 4, 5).collect(Collectors.toSet());
  • toMap,将Stream转化成Map,实际类型是HashMap
Stream.of(1, 2, 3, 4, 5).collect(Collectors.toMap(x -> x, x -> x * x))
  • toConcurrentMap,将Stream转化为ConcurrentHashMap
Stream.of(1, 2, 3, 4, 5).collect(Collectors.toConcurrentMap(x -> x, x -> x * x))
  • toCollection,将Stream转化为指定类型的容器,通过提供supplier来实现
Stream.of(1, 2, 3).collect(Collectors.toCollection(TreeSet::new));
  • collectingAndThen,在传入的Collector基础之上再执行一个finisher操作
Stream.of(1, 2, 3).collect(Collectors.collectingAndThen(Collectors.averagingInt(x -> x), x -> x.intValue()));
  • mapping, 适配一个已有的Collector,对Stream中元素执行mapper操作后再进行处理
Stream<String> stream = Stream.of("hello", "world");
stream.collect(Collectors.mapping(x -> x.length(), Collectors.toList()));
  • reducing,执行reduce操作
Stream.of(1, 2, 3).collect(Collectors.reducing((x, y) -> x + y)).ifPresent(System.out::println);
  • groupingBy,对Stream中元素进行分组
public class Java8 {

    public static class Sample {
        public int score;
        public int type;
        public Sample(int score, int type) {
            this.type = type;
            this.score = score;
        }

        @Override
        public String toString() {
            return "Sample{" +
                    "score=" + score +
                    ", type=" + type +
                    '}';
        }
    }

    public static void main(String[] args) throws Exception {
        List<Sample> samples = Arrays.asList(
                new Sample(90, 1),
                new Sample(80, 1),
                new Sample(80, 1),
                new Sample(70, 2),
                new Sample(60, 2),
                new Sample(50, 3));

        // 简单分组
        // {1=[Sample{score=90, type=1}, Sample{score=80, type=1}, Sample{score=80, type=1}], 2=[Sample{score=70, type=2}, Sample{score=60, type=2}], 3=[Sample{score=50, type=3}]}
        Map<Integer, List<Sample>> one = samples.stream().collect(Collectors.groupingBy(x -> x.type));
        System.out.println(one);
        
        // 二级分组,先按类别分组,再按分数分组
        // {1={80=[Sample{score=80, type=1}, Sample{score=80, type=1}], 90=[Sample{score=90, type=1}]}, 2={70=[Sample{score=70, type=2}], 60=[Sample{score=60, type=2}]}, 3={50=[Sample{score=50, type=3}]}}
        Map<Integer, Map<Integer, List<Sample>>> second = samples.stream().collect(Collectors.groupingBy(x -> x.type, Collectors.groupingBy(x -> x.score)));
        System.out.println(second);
        
        // 配合其余Collectors里的接口使用
        // 分组后求和
        // {1=250, 2=130, 3=50}
        Map<Integer, Integer> third = samples.stream().collect(Collectors.groupingBy(x -> x.type, Collectors.summingInt(x -> x.score)));
        System.out.println(third);
        
        // 分组后获取最大值
        // {1=Optional[Sample{score=90, type=1}], 2=Optional[Sample{score=70, type=2}], 3=Optional[Sample{score=50, type=3}]}
        Map<Integer, Optional<Sample>> forth = samples.stream().collect(Collectors.groupingBy(x -> x.type, Collectors.maxBy((x, y) -> x.score - y.score)));
        System.out.println(forth);
        
        // 分组后reduce
        // {1=Optional[250], 2=Optional[130], 3=Optional[50]}
        Map<Integer, Optional<Integer>> fifth = samples.stream().collect(Collectors.groupingBy(x -> x.type, Collectors.mapping(x -> x.score, Collectors.reducing((x, y) -> x + y))));
        System.out.println(fifth);
    }
}
  • partitioningBy,特殊的分组,将Stream元素分成true、false两组,其余支持的操作同groupingBy
// 分成true/false两组
// {false=[Sample{score=70, type=2}, Sample{score=60, type=2}, Sample{score=50, type=3}], true=[Sample{score=90, type=1}, Sample{score=80, type=1}, Sample{score=80, type=1}]}
Map<Boolean, List<Sample>>  sixth = samples.stream().collect(Collectors.partitioningBy(x -> x.type == 1));
System.out.println(sixth);

总结

Collectors里提供了不少功能,特别是groupingBy与其它接口的组合使用,可以实现一些复杂的数据处理与统计需求。有那么一点Linq的意思。不过在真正应用了groupingBy之后,觉得这种实现方式未必比以前直白繁琐的方式强,也许在实际项目中应用之后会有不同的体会。