上篇文章在介绍Stream中的相关API时,有两个collect方法没有讲解到,是因为与它们相关的Collector用处很多并且相对于其他的stream操作比较复杂,所以单独来介绍。其实除了Collector,还有Collectors,我们会放到一起来看。
我们先来看一下Collector的使用,看看我们能用它实现什么。

Collector的定义

我们先来看之前没有讲到的Stream中的collect方法,Stream中是有两个collect方法的,我们重点来看下面这个:

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

它接收的是一个Collector接口类型的参数,与Stream中很多其他方法的参数不同,Collector不是一个函数式接口,但是它内部却有几个函数式,这几个函数式接口以方法形式存在,这几个函数式接口在Collector中有不同的作用,我们这里来看下这几个返回函数式接口的方法:

  1. public interface Collector<T, A, R> {
  2. Supplier<A> supplier();
  3. BiConsumer<A, T> accumulator();
  4. BinaryOperator<A> combiner();
  5. Function<A, R> finisher();

这几个函数式接口我们应该已经很熟悉了,Stream中的很多方法会用到它们,例如BinaryOperator就在reduce方法中用到了:

  1. T reduce(T identity, BinaryOperator<T> accumulator);

在Collector中,这几个函数式接口是有依赖关系的,这里我们就先大致知道Collector是有这四个函数式接口,对这几个函数式接口提供不同的实现就能实现不同的功能,至于这几个接口执行的顺序和依赖关系,我们在看完Collector的使用后再来研究。

将Stream转换成其他集合

转成List

我们可以使用Collector将Stream转换为其他集合,例如List,看下面的例子:

  1. public static void main(String[] args) {
  2. Stream<String> stringStream = Stream.of("one","two","three","four","five");
  3. List<String> strings = stringStream.collect(Collectors.toList());
  4. System.out.println(strings);
  5. }

输出:
image.png
这样就将Stream转换为List了,注意看collect的方法参数,是Collectors.toList(),这里就用到了我们上面说的Collectors了,它提供了很多使用的方法,这些方法都返回特定的Collector来完成一些特定的操作,看下图:
image.png
我们这篇文章涉及到的很多Collector都是Collectors提供的。

转成Set

除了转换成List,还能转成Set,只需要使用toSet方法即可:

  1. Stream<String> stringStream = Stream.of("one","two","three","four","five");
  2. Set<String> strings = stringStream.collect(Collectors.toSet());

转成指定集合类型

toList和toSet方法都不需要我们指定任何参数,就能转成我们想要的集合。如果我们想要指定集合类型,
可以使用toCollection方法:

  1. public static void main(String[] args) {
  2. Stream<String> stringStream = Stream.of("one","two","three","four","five");
  3. HashSet<String> collect = stringStream.collect(Collectors.toCollection(HashSet::new));
  4. }

这里我们转换成了HashSet。

将元素分组

默认分组

我们可以使用groupby方法对Stream中的元素进行分类:

  1. public static void main(String[] args) {
  2. Stream<String> stringStream = Stream.of("one","two","three","four","five");
  3. Map<Integer, List<String>> collect = stringStream.collect(Collectors.groupingBy(s -> s.length()));
  4. System.out.println(JSON.toJSONString(collect));
  5. }

输出:
image.png
它的意思是按照流中的元素,这里是String,的长度进行分类。
它会生成一个Map<>类型,Map的key的类型是我们参数中给定的,这里是s.length(),即Integer,map的value的类型是List。

指定分组集合和分组元素

上面的默认分类会根据我们的分类依据(例如字符串的长度)将流中的元素分类为一个一个的list。还看上面的例子,如果我们想分类之后不是一个一个的list,而是其他集合,例如set,还想每个集合里面保存的不是string,而是其他类型,这个其他类型我们可以通过string获取,可以使用另外一个groupingBy方法:

  1. public static void main(String[] args) {
  2. Stream<String> stringStream = Stream.of("one","two","three","four","five");
  3. Map<Integer, Set<Integer>> result = stringStream.collect(Collectors.groupingBy(s -> s.length(), Collectors.mapping(s -> s.indexOf("o"), Collectors.toSet())));
  4. System.out.println(JSON.toJSONString(result));
  5. }

在这个例子里,我们还是根据string的长度对Stream中的元素进行分类,只不过分类后的集合不再是默认的list,而是set,并且集合中的元素不是String了,而是通过string.indexOf方法得到的int。
这个用到了Collectors.mapping,它也会产生一个Collector,这里groupBy使用它作为下游的collector,我们稍后会详细讲解它。

将元素分块

默认分块

分块与分组类似,但是也有不同。分块是将元素分为两部分,按照一个返回值为true和false的条件来进行,而分组是将元素按照某个条件(不一定是boolean)分为多组。
partitioningBy方法可以完成分组。

  1. public static void main(String[] args) {
  2. Stream<String> stringStream = Stream.of("one","two","three","four","five");
  3. Map<Boolean, List<String>> result = stringStream.collect(Collectors.partitioningBy(s -> s.length() > 3));
  4. System.out.println(JSON.toJSONString(result));
  5. }

在这个示例中,我们将字符串Stream按照元素的长度是否大于3进行分类。输出:
image.png
这样返回的Map的key是Boolean类型,value是List类型。默认情况下,分块会将每块的元素放到一个List中。

指定分块集合

与groupingBy一样,我们也可以通过一个下游的Collector指定分块元素的集合类型,另外一个partitioningBy方法可以达到这个目的:

  1. public static void main(String[] args) {
  2. Stream<String> stringStream = Stream.of("one","two","three","four","five");
  3. Map<Boolean, Set<String>> result = stringStream.collect(Collectors.partitioningBy(s -> s.length() > 3,Collectors.toSet()));
  4. System.out.println(JSON.toJSONString(result));
  5. }

对字符串的处理

直接拼接

Collectors提供了joining方法来处理字符串,主要是对字符串进行拼接操作,我们来看一个最简单的:

  1. public static void main(String[] args) {
  2. Stream<String> stringStream = Stream.of("one","two","three","four","five");
  3. String result = stringStream.collect(Collectors.joining());
  4. System.out.println(JSON.toJSONString(result));
  5. }

这个joining没有参数,看结果:
image.png

增加分隔符

我们还可以在拼接时增加一个分隔符,用到的是一个参数的joining方法:

  1. public static void main(String[] args) {
  2. Stream<String> stringStream = Stream.of("one","two","three","four","five");
  3. String result = stringStream.collect(Collectors.joining("-"));
  4. System.out.println(JSON.toJSONString(result));
  5. }

结果:
image.png

增加前缀和后缀

最后一个joining方法可以指定拼接时的前缀和后缀:

  1. public static void main(String[] args) {
  2. Stream<String> stringStream = Stream.of("one","two","three","four","five");
  3. String result = stringStream.collect(Collectors.joining("-","[","]"));
  4. System.out.println(JSON.toJSONString(result));
  5. }

结果:
image.png

对数字的处理

对数字的处理,Collectors提供了求和、求平均值等方法,并且对于int、long、double都有对应的函数。我们这里主要看int类型的。

求和

summingInt方法可以用来对数据进行求和:

  1. public static void main(String[] args) {
  2. Stream<String> stringStream = Stream.of("one","two","three","four","five");
  3. Integer result = stringStream.collect(Collectors.summingInt(s -> s.length()));
  4. System.out.println(result);
  5. }

它接收一个

  1. ToIntFunction<? super T> mapper

来将流中的元素转换为Int。输出:
image.png

求平均值

averagingInt可以用来求平均值,用法与summingInt一致。

  1. public static void main(String[] args) {
  2. Stream<String> stringStream = Stream.of("one","two","three","four","five");
  3. Double aDouble = stringStream.collect(Collectors.averagingInt(s -> s.length()));
  4. System.out.println(aDouble);
  5. }