介绍 Java 8 Collectors 类

介绍 Java 8 Collectors 类

本文我们探讨Java 8 Collectors 类,其一般用于流处理中的最后一步。

Stream.collect() 方法

Stream.collect() 是java 8 stream api中的终止方法。它实现对流实例中保存的数据元素执行可变的折叠操作(将元素重新包装至特定的数据结构中,并应用一些额外逻辑将它们连接起来,等等)。此操作的策略由Collector接口实现提供。

Collectors

在Collectors类中有所有的预定义实现。一般常见做法是使用下面的静态导入来提高可读性:

import static java.util.stream.Collectors.*;

或者单个导入你选择的collectors:

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;

下面所有示例我们将使用该集合:

List<String> givenList = Arrays.asList("a", "bb", "ccc", "dd");

Collectors.toList()

ToList 方法可以收集所有流中元素至List实例。需要注意的是,使用该方法不能假设任何List的特定实现,如果你想指定List实现,需要使用toCollection 代替.下面示例创建一个流实例表示元素序列,然后收集至List实例对象中。

List<String> result = givenList.stream().collect(toList());

Collectors.toSet()

ToSet 方法可以收集所有流中元素至Set 实例.需要注意的是,使用该方法不能假设任何Set的特定实现,如果你想指定Set实现,需要使用toCollection 代替.下面示例创建一个流实例表示元素序列,然后收集至Set例对象中

Set<String> result = givenList.stream().collect(toSet());

Set不包含重复元素。如果集合中有重复元素,则set中仅保留一个:

List<String> listWithDuplicates = Arrays.asList("a", "bb", "c", "d", "bb");
Set<String> result = listWithDuplicates.stream().collect(toSet());
assertThat(result).hasSize(4);

Collectors.toCollection()

你可能已经注意到,使用toSet 和 toList收集器,不能指定其实现。如果想使用特定实现,需要使用toCollection收集器并提供特定集合实现。下面示例创建一个流实例表示元素序列,然后收集至LinkedList例对象中

List<String> result = givenList.stream().collect(toCollection(LinkedList::new))

注意这里不能使用不可变集合实现。通过自定义Collector实现或使用collectingAndThen。

Collectors.toMap()

ToMap 收集器用于收集流元素至 Map 实例,实现该功能需要提供两个函数:

  • keyMapper
  • valueMapper

keyMapper 用于从流元素中抽取map 的key,valueMapper抽取与可以关联的value。
下面示例收集流元素至Map中,存储字符串作为key,其长度作为value:

Map<String, Integer> result = givenList.stream()
  .collect(toMap(Function.identity(), String::length))

Function.identity() 是一个预定义的返回接收参数的快捷函数。
如果我们集合中包含重复元素会怎么样?与toSet相反,toMap不能过滤重复元素。这个比较好理解————其如何确定key关联那个value?

List<String> listWithDuplicates = Arrays.asList("a", "bb", "c", "d", "bb");
assertThatThrownBy(() -> {
    listWithDuplicates.stream().collect(toMap(Function.identity(), String::length));
}).isInstanceOf(IllegalStateException.class);

我们看到,toMap甚至不判断值是否相等,如果key重复,立刻抛出IllegalStateException异常。当key冲突时,我们应该使用toMap的另一个重载方法:

Map<String, Integer> result = givenList.stream()
  .collect(toMap(Function.identity(), String::length, (item, identicalItem) -> item));

第三个参数是二元操作,我们可以指定如何处理key冲突。本例中我们可以任意选择一个,因为两者的长度始终一样。

Collectors.collectingAndThen()

CollectingAndThen 是一个特殊收集器,其可以收集完成后再结果上执行另外动作。下面收集流元素至List示例,然后转换结果为ImmutableList实例:

List<String> result = givenList.stream()
  .collect(collectingAndThen(toList(), ImmutableList::copyOf))

Collectors.joining()

Joining 收集器可以用于连接字符串流Stream中的元素。示例如下:

String result = givenList.stream()
  .collect(joining());

结果如下:

"abbcccdd"

还可以指定分隔符、前缀和后缀:

String result = givenList.stream()
  .collect(joining(" "));

结果如下:

"a bb ccc dd"

再看一个示例:

String result = givenList.stream()
  .collect(joining(" ", "PRE-", "-POST"));

结果为:

"PRE-a bb ccc dd-POST"

Collectors.counting()

Counting 是一个简单收集器,返回元素数量:

Long result = givenList.stream()
  .collect(counting());

Collectors.summarizingDouble/Long/Int()

SummarizingDouble/Long/Int 收集器返回流中抽取的数值元素的统计结果类型。下面示例获取字符串长度信息:

DoubleSummaryStatistics result = givenList.stream()
  .collect(summarizingDouble(String::length));

下面测试代码执行通过:

assertThat(result.getAverage()).isEqualTo(2);
assertThat(result.getCount()).isEqualTo(4);
assertThat(result.getMax()).isEqualTo(3);
assertThat(result.getMin()).isEqualTo(1);
assertThat(result.getSum()).isEqualTo(8);

Collectors.averagingDouble/Long/Int()

AveragingDouble/Long/Int 是返回流中抽取元素的平均值,下面示例计算字符串长度平均值:

Double result = givenList.stream()
  .collect(averagingDouble(String::length));

Collectors.summingDouble/Long/Int()

SummingDouble/Long/Int 返回抽取元素之和,下面计算所有字符串长度之和:

Double result = givenList.stream()
  .collect(summingDouble(String::length));

Collectors.maxBy()/minBy()

MaxBy/MinBy 收集器根据提供的 Comparator 实例,返回流中最大和最小元素,下面计算最大元素:

Optional<String> result = givenList.stream()
  .collect(maxBy(Comparator.naturalOrder()));

需要注意的是,其结果为Optional类型,其强制用户考虑空集合情况

Collectors.groupingBy()

分组收集器用于根据属性对元素进行分组并存储在Map实例中,下面示例根据字符串长度进行分组,并把结果存储在Set中:

Map<Integer, Set<String>> result = givenList.stream()
  .collect(groupingBy(String::length, toSet()));

测试代码如下:

assertThat(result)
  .containsEntry(1, newHashSet("a"))
  .containsEntry(2, newHashSet("bb", "dd"))
  .containsEntry(3, newHashSet("ccc"));

第二个参数是一个集合,我们开使用任何集合实现。

Collectors.partitioningBy()

PartitioningBy 是一个特殊分组收集器,依据 Predicate 实例收集流元素至Map中,存储 Boolean 值作为key,值为集合。 “true” 键对应值为已匹配元素的集合,“false” 键为非匹配元素集合。示例代码如下:

Map<Boolean, List<String>> result = givenList.stream()
  .collect(partitioningBy(s -> s.length() > 2))

结果Map如下:

{false=["a", "bb", "dd"], true=["ccc"]}

Collectors.teeing()

根据目前我们已经学习的内容求最大值和最小值:

List<Integer> numbers = Arrays.asList(42, 4, 2, 24);
Optional<Integer> min = numbers.stream().collect(minBy(Integer::compareTo));
Optional<Integer> max = numbers.stream().collect(maxBy(Integer::compareTo));
// do something useful with min and max

这里,我们使用两个不用的收集器,然后合并集合去实现相应业务。在Java 12之前,为了实现这样功能必须对流执行两次操作,把中间结果存储至临时对象中,最后合并返回。
幸运的是,java 12 提供了内置收集器帮助我们处理这些步骤:我们仅需提供两个收集器和合并函数。
因为新的收集器tee对流执行两个不用方向的操作,故称为T收集器:

numbers.stream().collect(teeing(
  minBy(Integer::compareTo), // The first collector
  maxBy(Integer::compareTo), // The second collector
  (min, max) -> // Receives the result from those collectors and combines them
));

总结

本文我们学习了Collectors类及其内置的各种收集器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值