Java 8 函数式编程(函数式接口使用 - Stream 中collect 方法的使用)

328 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情

之前的几篇文章我们介绍了reduce 方法的使用,这篇文章我们开始介绍collect 方法。

Stream 对象常用的结束操作方法

collect 方法 的概念

collect 的中文意思是“聚集;收藏;领取、接走;获取;收取”等含义。这个方法的作用也是差不多相关的意思。

collect 方法是一个结束(终止)操作。比如:将Stream 中的元素放到一个集合(Set/List/Map)当中;将Stream 中的元素进行分组;对Stream 中的元素进行求和等操作。

Streamcollect 方法有两种重载实现:

<R, A> R collect(Collector<? super T, A, R> collector);

<R> R collect(Supplier<R> supplier,
              BiConsumer<R, ? super T> accumulator,
              BiConsumer<R, R> combiner);

首先我们来介绍第一个重载方法。

<R, A> R collect(Collector<? super T, A, R> collector);

这个重载实现中,我们发现,collect 方法需要接收一个收集器Collector 的实例。Collector 本身是一个接口,它是用来定义一个可变的汇聚操作,我们现在围绕这个接口来展开讲解。

我们首先来看它的接口定义(这里只留一部分重要的代码在):

public interface Collector<T, A, R> {

  Supplier<A> supplier();
  BiConsumer<A, T> accumulator();
  BinaryOperator<A> combiner();
  Function<A, R> finisher();
  Set<Characteristics> characteristics();

  public static <T, R> Collector<T, R, R> of(Supplier<R> supplier,
                                             BiConsumer<R, T> accumulator,
                                             BinaryOperator<R> combiner,
                                             Characteristics... characteristics) {
    // ......
  }

  public static <T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
                                                BiConsumer<A, T> accumulator,
                                                BinaryOperator<A> combiner,
                                                Function<A, R> finisher,
                                                Characteristics... characteristics) {
    // .......
  }

  enum Characteristics {
    CONCURRENT,
    UNORDERED,
    IDENTITY_FINISH
  }
}

上面五个方法的作用分别为:

  • supplier:创建并且返回一个可变结果容器
  • accumulator:将一个值添加进一个可变结果容器
  • combiner:接受两部分结果,然后将它们合并。可能是将一个参数添加到另一个参数中,然后返回另一个参数;也可能返回一个新的结果容器,这种在多线程处理的时候会用到。
  • finisher:对某个中间类型执行最终的转换,将其转换为最终的结果类型。如果设置了属性。IDENTITY_TRANSFORM,那么该方法会假定中间结果类型可以强制转换为最终结果的类型。
  • characteristics:这个是收集器的属性集合。

我们可以发现,Collector 接口中定义了5个方法,这五个方法也是我们开发中会经常遇到的。Streamcollect 方法就是调用这个接口中的各个方法,根据不同的需求,用不同的实现,来实现汇聚操作

collect 方法的使用

前面内容中我们介绍了collect 的一个重载方法的定义:

<R, A> R collect(Collector<? super T, A, R> collector);

在这个实现中,我们需要传入一个Collector 接口的实例。但是我们经常都会用Collectors 来构造Collector 收集器。(注意两个词的s 的区别)

Collectors 可以认为是Collector 的工厂,里面有大量的静态方法,这些静态方法最的返回值都是一个Collector 收集器,所以说我们可以认为CollectorsCollector 收集器的一个工厂类。

Collectors 里面定义了一个静态内部类CollectorImpl,该类是Collector收集器的一个实现。这里我们简单地看一下它的代码:

static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
        private final Supplier<A> supplier;
        private final BiConsumer<A, T> accumulator;
        private final BinaryOperator<A> combiner;
        private final Function<A, R> finisher;
        private final Set<Characteristics> characteristics;

        CollectorImpl(Supplier<A> supplier,
                      BiConsumer<A, T> accumulator,
                      BinaryOperator<A> combiner,
                      Function<A,R> finisher,
                      Set<Characteristics> characteristics) {
            this.supplier = supplier;
            this.accumulator = accumulator;
            this.combiner = combiner;
            this.finisher = finisher;
            this.characteristics = characteristics;
        }

        CollectorImpl(Supplier<A> supplier,
                      BiConsumer<A, T> accumulator,
                      BinaryOperator<A> combiner,
                      Set<Characteristics> characteristics) {
            this(supplier, accumulator, combiner, castingIdentity(), characteristics);
        }

        @Override
        public BiConsumer<A, T> accumulator() {
            return accumulator;
        }

        @Override
        public Supplier<A> supplier() {
            return supplier;
        }

        @Override
        public BinaryOperator<A> combiner() {
            return combiner;
        }

        @Override
        public Function<A, R> finisher() {
            return finisher;
        }

        @Override
        public Set<Characteristics> characteristics() {
            return characteristics;
        }
    }

对于这个内部类,jdk 给出的英文注释为:Simple implementation class for Collector.

我们同时可以看到,它对Collector 中定义的5 个方法的返回类型,定义了五个相同的对应类型的成员变量。

在这里面,这五个方法的实现是直接返回对应的5 个成员变量。而这五个对象是在构造CollectorImpl 的时候传进来的,这些成员变量的对象都是函数式接口类型。

看代码的同时我们也可以看到Collectors 中的静态方法其实很多就是new 一个CollectorImpl,然后将这个对象返回,而Collector 中抽象方法的实现直接以lambda 表达式的形式直接通过CollectorImpl 构造方法的参数传过去。

这里我们拿Collectors.toList() 举例进行分析,我们看下它的代码:

public static <T>
Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                               (left, right) -> { left.addAll(right); return left; },
                               CH_ID);
}

我们对这个实现进行分析:

  • supplier 方法参数的的实现为:ArrayList::new,他的作用就是创建一个ArrayList 对象并返回。
  • accumulator 的实现为:List::add,它将流中的元素添加进刚刚创建的ArrayList 对象。
  • combiner 方法的实现为:(left, right) -> { left.addAll(right); return left; },如果读者看过前几篇文章,它的作用应该就很好理解了。它是对两个中间结果容器ArrayList 进行处理的,将其中一个list 的所有元素添加进另外一个list,并返回另外一个list
  • finisher 需要跟进到代码里面的实现,它的作用只是将ArrayListList 类型返回。
  • characteristics 方法的实现仅仅是返回静态常量CH_ID,它是本身是一个集合,包含了IDENTITY_FINISH,它表示处理得到的中间结果是可以直接向最终结果进行强制类型转换的。

Collectors 类中常用的静态方法介绍

1. Collectors.toSet()

Stream 中的元素转为Set 类型的集合。注意转成SetSet 集合的性质(无重复元素)。

2. Collectors.toMap(x -> x, x -> x + 1)

Stream 中的元素转换成Map 结构,举例如下:

Map<Integer, Integer> map = Stream.of(1, 2, 3).collect(Collectors.toMap(x -> x, x -> x * 10));

3. Collectors.minBy(Integer::compare)

这个方法的作用就是寻找Stream 中的最小值,与之对应的是maxBy 函数,就是寻找Stream 中元素的最大值。

4. Collectors.averagingInt(x->x)

这个方法是求Stream 中的元素的平均值,当然对应也有averagingDoubleaveragingLong方法。

5. Collectors.summingInt(x -> x))

这个方法的功能是求Stream 中元素的和,与之对应的有summingDoublesummingLong

6. Collectors.summarizingDouble(x -> x)

这个方法用来求Stream 序列中的一些总结分析的数据,如最大值、最小值、平均值等等,参考如下代码:

IntSummaryStatistics intSummaryStatistics = Stream.of(1, 2, 3).collect(Collectors.summarizingInt(Integer::valueOf));
intSummaryStatistics.getSum();
intSummaryStatistics.getMax();
intSummaryStatistics.getAverage();

7. Collectors.groupingBy(x -> x)

这个函数的作用相当于sql 的groupBy,对于Collectors 中的groupingBy 方法有三种重载方法实现。对应分别有1 ~ 3 个参数。这举例说明

public static void main(String[] args) {

  Stream<Integer> stream = Stream.of(1, 2, 3, 3, 4);
  Map<Integer, List<Integer>> map = stream.collect(Collectors.groupingBy(x -> x));
  System.out.println(map);

  Stream<Integer> stream1 = Stream.of(1, 2, 3, 3, 4);
  Map<Integer, Long> map1 = stream1.collect(Collectors.groupingBy(x -> x, Collectors.counting()));
  System.out.println(map1);

  Stream<Integer> stream2 = Stream.of(1, 2, 3, 3, 4);
  HashMap<Integer, Long> map2 = stream2.collect(Collectors.groupingBy(x -> x,HashMap::new, Collectors.counting()));
  System.out.println(map2);
}

输出结果为:

{1=[1], 2=[2], 3=[3, 3], 4=[4]}
{1=1, 2=1, 3=2, 4=1}
{1=1, 2=1, 3=2, 4=1}

8. Collectors.joining("xxx")

这个函数的作用是使用传入的字符来拼接字符串,示例代码如下:

public static void main(String[] args) {
  Stream<String> stream = Stream.of("1", "2", "3", "4");
  String result = stream.collect(Collectors.joining("+"));
  System.out.println(result);
}

输出结果为:

1+2+3+4

以上就是Collectors 类中一些常用静态方法的介绍。如果读者还需要一些其他功能,可以自行阅读代码,寻找合适的方法来完成需求。

这篇文章中描述的对于collect 方法的另外一个重载方法:

<R> R collect(Supplier<R> supplier,
              BiConsumer<R, ? super T> accumulator,
              BiConsumer<R, R> combiner);

因为日常使用的并不是很多,所以就不做展开介绍了。

总结

至此,Stream 中的collect 方法相关的内容就介绍完了。同时,关于java 8 函数式编程接口的使用之Stream 的使用的介绍也告一段落。其实在我们日常开发中,使用Stream 的位置特别多,最近这几篇文章介绍的内容其实是比较基础的,希望读者一定要将它们牢记,从而在工作或者学习中,不论是自己开发还是阅读其他人的代码,让这些知识不会变成我们的绊脚石。

接下来我们会开始介绍Optional 的使用。