Stream.collect()是Java 8的Stream API的终端方法之一。 它使我们能够对Stream实例中保存的数据元素执行可变的折叠操作(将元素重新打包到某些数据结构中,并且应用一些额外的逻辑,串接数据等)。 该操作的具体策略是通过Collector接口的实现来提供。
Collectors
所有预定义的实现都可以在_Collectors_类中找到。 通常的做法是将以下静态导入与这些方法结合使用,以提高可读性:
import static java.util.stream.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收集器可用于将所有Stream元素收集到List实例中。需要注意的是,我们使用此方法时不能假设任何特定的List实现。如果要对此进行控制,请使用toCollection方法。 我们先创建包含一系列元素的流实例,并将其中元素收集到一个List实例中:
List<String> result = givenList.stream() .collect(toList());复制代码
Java 10引入了一种方便的方法可以将Stream中元素收集到一个unmodifiable List中:
List<String> result = givenList.stream() .collect(toUnmodifiableList()); assertThatThrownBy(() -> result.add("foo")).isInstanceOf(UnsupportedOperationException.class);复制代码
Collectors.toSet()
toSet收集器可用于将所有Stream元素收集到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);复制代码
Java 10中也提供了_toUnmodifiableSet()_ 方法来创建unmodifiable Set:
Set<String> result = givenList.stream() .collect(toUnmodifiableSet()); assertThatThrownBy(() -> result.add("foo")).isInstanceOf(UnsupportedOperationException.class);复制代码
Collectors.toCollection()
前面提到了,在使用toSet和toList收集器时,您无法对其实现进行任何假设。 如果要指定实现类型,则需要使用toCollection收集器。 我们来将流中元素收集到一个LinkedList实例中:
List<String> result = givenList.stream() .collect(toCollection(LinkedList::new))复制代码
请注意,这不适用于任何不可变的集合。 对于这种情况,您将需要编写自定义的Collector实现或使用collectionAndThen。
Collectors.toMap()
toMap收集器可用于将流元素收集到Map实例中。为此,我们需要提供两个函数:
- keyMapper
- valueMapper
keyMapper用于从流元素中提取映射键,valueMapper用于提取与给定键相关联的值。 让我们将这些元素收集到一个Map中,该Map将字符串作为键,并将其长度存储为值:
Map<String, Integer> result = givenList.stream() .collect(toMap(Function.identity(), String::length))复制代码
Function.identity()只是用于定义接受参数和返回值相同的函数的快捷方式。 如果我们的集合包含重复元素,会发生什么? 与toSet不同,toMap操作不会默默过滤重复项。 这是可以理解的——程序应该如何确定为该key选择哪个值?
List<String> listWithDuplicates = Arrays.asList("a", "bb", "c", "d", "bb"); assertThatThrownBy(() -> { listWithDuplicates.stream().collect(toMap(Function.identity(), String::length)); }).isInstanceOf(IllegalStateException.class);复制代码
请注意,toMap甚至不会评估值是否也相等。 如果看到重复的键,则会立即引发IllegalStateException。 在发生键冲突的情况下,我们应该使用toMap的另一种形式:
Map<String, Integer> result = givenList.stream() .collect(toMap(Function.identity(), String::length, (item, identicalItem) -> item));复制代码
这里的第三个参数是BinaryOperator,我们可以在其中指定如何处理冲突。 在上面描述的场景下,我们可以选择这两个冲突值中的任何一个,因为我们知道相同的字符串也将始终具有相同的长度。 与List和Set类似,Java 10引入了一种简单的方法来将Stream元素收集到unmodifiable Map中:
Map<String, Integer> result = givenList.stream() .collect(toMap(Function.identity(), String::length)); assertThatThrownBy(() -> result.put("foo", 3)) .isInstanceOf(UnsupportedOperationException.class);复制代码
Collectors.c_ollectingAndThen()_
collingandthen是一个特殊的收集器,它允许在收集结束后直接对结果执行另一个操作。 让我们将流元素收集到一个List实例中,然后将结果转换为一个ImmutableList实例:
List<String> result = givenList.stream() .collect(collectingAndThen(toList(), ImmutableList::copyOf))复制代码
Collectors.j_oining()_
Joining收集器可用于联接Stream 元素。 我们可以通过以下方式将他们加入一起:
String result = givenList.stream() .collect(joining());// 结果为 "abbcccdd"复制代码
还可以指定自定义分隔符,前缀,后缀:
String result = givenList.stream() .collect(joining(" ", "PRE-", "-POST"));// 结果为 "PRE-a bb ccc dd-POST"复制代码
数学类收集器
Counting收集器用于对流中的元素进行计数;
Long result = givenList.stream() .collect(counting());复制代码
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);复制代码
AveragingDouble/Long/Int是用于返回元素属性的平均值的收集器。如计算字符串平均长度:
Double result = givenList.stream() .collect(averagingDouble(String::length));复制代码
SummingDouble/Long/Int是返回所提取元素之和的收集器。计算字符串长度之和:
Double result = givenList.stream() .collect(summingDouble(String::length));复制代码
比较类收集器
MaxBy/MinBy收集器根据提供的Comparator实例返回Stream的最大/最小元素。 可以通过以下方式筛选最大元素:
Optional<String> result = givenList.stream() .collect(maxBy(Comparator.naturalOrder()));复制代码
要注意,返回值被封装为Optional类型,这也迫使用户重新思考空集合这类边界情况。
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"));复制代码
PartitioningBy是groupingBy的一种特殊情况,它接受一个谓词实例并将Stream元素收集到一个Map实例中,该Map将布尔值作为键,将分组集合作为值。 在“ true”键下,您可以找到与给定谓词匹配的元素集合,在“ false”键下,您可以找到与给定谓词不匹配的元素集合。
Map<Boolean, List<String>> result = givenList.stream() .collect(partitioningBy(s -> s.length() > 2))复制代码
结果Map中内容为:
{false=["a", "bb", "dd"], true=["ccc"]}复制代码
自定义收集器
如果你要自定义收集器实现,则需要实现Collector接口并指定其三个泛型参数:
public interface Collector<T, A, R> {...}复制代码
- T –可用于收集的对象类型,
- A –可变累加器对象的类型,
- R –最终结果的类型。
我们可以实现一个收集器,将元素收集到_ImmutableSet_中,首先声明泛型类型:
private class ImmutableSetCollector<T> implements Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>> {...}复制代码
由于我们需要一个可变的集合来进行处理收集器内部的集合操作,因此我们不能使用ImmutableSet。 我们需要使用其他可变集合或任何可以临时存储对象的类。 这里,我们选择使用ImmutableSet.Builder,现在我们需要实现5个方法:
- Supplier<ImmutableSet.Builder> supplier()
- BiConsumer<ImmutableSet.Builder, T> accumulator()
- BinaryOperator<ImmutableSet.Builder> combiner()
- Function<ImmutableSet.Builder, ImmutableSet> finisher()
- Set characteristics()
各方法的作用和要求为: supplier()方法要返回一个supplier实例,该实例会生成一个空的accumulator实例,因此,在本例中,我们可以简单地返回一个builder对象。 accumulator()方法会返回一个函数,该函数用于将新元素添加到现有的accumulator对象中,因此我们可以使用Builder的add方法。 combiner()方法返回一个函数,该函数用于将两个accumulator中的元素合并在一起。 finisher()方法返回一个用于将累加器accumulator转换为最终结果类型的函数,因此在本例中,我们将使用构建器的build()方法。 characteristic()方法用于为流提供一些额外的信息,这些信息将用于内部优化。在这种情况下,我们不关注Set中的元素顺序,因此我们可以使用Characteristics.UNORDERED。 完整的实现如下:
public class ImmutableSetCollector<T> implements Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>> {@Overridepublic Supplier<ImmutableSet.Builder<T>> supplier() {return ImmutableSet::builder; }@Overridepublic BiConsumer<ImmutableSet.Builder<T>, T> accumulator() {return ImmutableSet.Builder::add; }@Overridepublic BinaryOperator<ImmutableSet.Builder<T>> combiner() {return (left, right) -> left.addAll(right.build()); }@Overridepublic Function<ImmutableSet.Builder<T>, ImmutableSet<T>> finisher() {return ImmutableSet.Builder::build; }@Overridepublic Set<Characteristics> characteristics() {return Sets.immutableEnumSet(Characteristics.UNORDERED); }public static <T> ImmutableSetCollector<T> toImmutableSet() {return new ImmutableSetCollector<>(); } }复制代码
可以测试该自定义收集器:
List<String> givenList = Arrays.asList("a", "bb", "ccc", "dddd"); ImmutableSet<String> result = givenList.stream().collect(toImmutableSet());复制代码