Stream 里的 collect() 方法

Stream.collect() 是 Java 8 Stream API 里的其中一个终结方法,利用它可以对保存在 Stream 实例中的数据元素执行多种折叠操作(将元素重新打包到某些数据结构并应用一些附加逻辑,连接这些元素等),操作使用的策略由 Collectors 接口实现。

Collectors 类中预定义的方法

下面的例子中均使用以下 List:

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

Collectors.toList()/Collectors.toSet()

toList/toSet 收集器用来将 Stream 中的全部元素收集到一个 List/Set 对象中,需要注意的是该方法无法指定收集的结果对象类型无法指定。

1
2
List<String> result = givenList.stream().collect(toList());
Set<String> result = givenList.stream().collect(toSet());
Collectors.toCollection()

相对于toList/toSet, toCollection 可以指定收集目标对象的具体类型。但是参数中不能指定不可变的集合对象。

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

Collectors.collectingAndThen()

collectingAndThen 方法可以在将结果收集后进行其它操作,因此也就可以通过这个方法将结果收集为不可变的集合对象。

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

Collectors.joining()

joining 方法表现类似于 StringJoiner,同样可以指定分隔符、前缀、后缀。

1
2
3
String result1 = givenList.stream().collect(joining());
String result2 = givenList.stream().collect(joining(" "));
String result3 = givenList.stream().collect(joining(" ", "PRE-", "-POST"));

1
2
3
result1: "abbcccdd"
result2: "a bb ccc dd"
result3: "PRE-a bb ccc dd-POST"
Collectors.toMap()

toMap 用于将结果收集成 Map 对象,需要实现两个函数:

  • keyMapper
  • valueMapper

keyMapper 将用于从 Stream 元素中提取 Map Key,valueMapper将用于提取与给定 Key 相关联的值。
将之前的字符串列表收集成以字符串本身为 key, 其长度为 value 的 Map。

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

Function.identity() 是一个返回传入值本身的函数。
toMap 方法收集成 Map 的过程中必须处理对于 key 冲突的情况,否则在出现重复 key 时会抛出异常。要处理冲突,只在方法中传入第三个参数,用于指定 key 冲突时 value 的选取方式。

1
2
Map<String, Integer> result = givenList.stream()
.collect(toMap(Function.identity(), String::length, (i1, i2) -> i1));

Collectors.counting()

counting 是一个简单的对 Stream 中元素个数的计数方法。

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

Collectors.averagingDouble/Long/Int()

averagingDouble/Long/Int 返回关于 Stream 中元素的数值数据平均值。

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

Collectors.summingDouble/Long/Int()

summingDouble/Long/Int 返回关于 Stream 中元素的数值数据累加值。

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

Collectors.maxBy()/minBy()

maxBy/minBy 返回关于 Stream 中元素的最大/最小值,元素的“大小”信息由传入的比较器确定。

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

注意:

  • Comparator.naturalOrder() 也是自 Java 8 开始引入,返回一个按照“自然排序”的比较器。
  • 返回值由 Optional 修饰,意味着流本身可能为空,因而返回值也可能是空值。
Collectors.summarizingDouble/Long/Int()

summarizingDouble/Long/Int 是一个返回单个特殊对象的收集器,对象中包含关于 Stream 中元素的数值数据统计信息。

1
IntSummaryStatistics result = givenList.stream().collect(summarizingInt(String::length));

传入的参数 String::length 是一个 Mapper,应用于流中的每个元素,将其转换为用于计算的目标类型。

1
IntSummaryStatistics{count=4, sum=8, min=1, average=2.000000, max=3}

Collectors.groupingBy()

groupingBy 收集器用于按某些属性对对象进行分组,并将结果存储在Map对象中。方法的第二个参数也是一个收集器,用于指定分组后的数据存储的目标对象。

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

上面的代码根据字符串的长度将字符串放入 Set 中:

1
{1=[a], 2=[bb, dd], 3=[ccc]}

Collectors.partitioningBy()

PartitioningBy 是 groupingBy 的特例,它接受一个 Predicate 对象并将 Stream 元素收集到一个 Map 对象中,该实例将 Boolean 值存储为 key 并将集合存储为 value。 在 “true” key 下可以找到与给定 Predicate 匹配的元素集合;在“false” key 下,可以找到与给定的 Predicate 不匹配的元素集合。

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

结果:

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

自定义 Collectors

Collector 接口原型:

1
public interface Collector<T, A, R> {...}

  • T - 被收集对象的类型,
  • A - 可变累加器对象的类型,
  • R - 最终结果的类型。

下面开始一步步编写一个收集器,将元素收集到ImmutableSet实例中。

1
2
public class ImmutableSetCollector<T>
implements Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>> {...}

由于我们需要一个用于内部收集操作处理的可变集合,因此不能直接使用目标类型ImmutableSet; 我们需要使用一些其他可变集合或任何其他可以暂时为我们收集对象的类型,这里选取 ImmutableSet.Builder。选取内部存储对象后,下一步是覆写5个抽象方法:

Supplier<ImmutableSet.Builder\> supplier()

supplier() 方法用于返回一个可用的内部存储对象,用于后续的内部操作,这里直接使用ImmutableSet.Builder

1
2
3
4
@Override
public Supplier<ImmutableSet.Builder<T>> supplier() {
return ImmutableSet::builder;
}

BiConsumer<ImmutableSet.Builder\, T> accumulator()

accumulator() 用于处理传入的对象和内部存储对象的累加操作:

1
2
3
4
@Override
public BiConsumer<ImmutableSet.Builder<T>, T> accumulator() {
return ImmutableSet.Builder::add;
}

BinaryOperator<ImmutableSet.Builder\> combiner()

combiner() 用于将两个内部的累加对象合并:

1
2
3
4
@Override
public BinaryOperator<ImmutableSet.Builder<T>> combiner() {
return (left, right) -> left.addAll(right.build());
}

Function<ImmutableSet.Builder\, ImmutableSet\> finisher()

finisher() 在累加完成后将结果输出:

1
2
3
4
@Override
public Function<ImmutableSet.Builder<T>, ImmutableSet<T>> finisher() {
return ImmutableSet.Builder::build;
}

Set\ characteristics()

characteristics() 返回指示此收集器特征的 Collector.Characteristics 不可变集合。示例收集器的输出是 Set ,因此在这里简单指定一个表示无序的特征:

1
2
3
@Override public Set<Characteristics> characteristics() {
return Sets.immutableEnumSet(Characteristics.UNORDERED);
}

使用
1
Set<String> result = givenList.stream().collect(new ImmutableSetCollector<>());