在Stream API能够帮助我们简化集合数据的处理,在处理流时有两种操作
- 中间操作
中间操作会返回另外一个流,这让多个操作可以连接起来,形成一个查询,中间操作调用之后并不会立即执行,会在执行终止操作时,一次性全部处理。例如filter
和sorted
都属于中间操作
- 终止操作
终止操作会从流的流水线生成结果。它的结果可以是任何不是流的值,例如List
,Integer
甚至是void
。collect()就是其中一个终止操作。
collect()方法
collect()
包含两个重载方法
方法一
collect()
方法1如下所示,它的入参是3个函数式接口
java
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
它的三个入参分别为
Supplier<R> supplier
提供一个新的结果容器的supplier,如果是并行流,这个函数会被调用多次,因此每次都需要返回一个新的容器
BiConsumer<R, ? super T> accumulator
合并元素到结果容器的函数
BiConsumer<R, R> combiner
合并两个结果容器consumer
上面的collect()相当于下面这段代码
java
R result = supplier.get();
for (T element : this stream)
accumulator.accept(result, element);
return result;
我们通过下面代码验证上面代码的执行情况
java
public static void main(String[] args) {
List<Integer> result = IntStream.range(1, 5)
.collect(
() -> {
System.out.println("====> 创建容器");
return new ArrayList<>();
},
((arrayList, value) -> {
System.out.println("====> 累加值:" + value + "\t累加结果数组:" + ListUtil.toString(arrayList));
arrayList.add(value);
}),
(list1, list2) -> {
System.out.println("<==== 结果合并:" + "结果1:" + ListUtil.toString(list1) + "\t结果2" + ListUtil.toString(list2));
list1.addAll(list2);
}
);
ListUtil.printArray(result, "----------> 输出结果");
}
执行结果如下所示
可以看到第三个consumer并没有被执行,在整个collect过程中,只创建了一个容器,然后将流中的数据添加到容器中,并不需要合并容器,将IntStream
改成并行流
java
List<Integer> result = IntStream.range(1, 5)
.parallel() // 改成并行流,其他不变
.collect(
() -> {
System.out.println("====> 创建容器");
return new ArrayList<>();
},
((arrayList, value) -> {
System.out.println("====> 累加值:" + value + "\t累加结果数组:" + ListUtil.toString(arrayList));
arrayList.add(value);
}),
(list1, list2) -> {
System.out.println("<==== 结果合并:" + "结果1:" + ListUtil.toString(list1) + "\t结果2" + ListUtil.toString(list2));
list1.addAll(list2);
}
);
ListUtil.printArray(result, "----------> 输出结果");
执行结果如下所示,在collect()过程创建了4个容器,执行了3次合并,将4个容器合并成最终结果容器并返回。
方法二
这个方法和上面的不同是入参只有一个,只需要传入一个Collector
接口
java
<R, A> R collect(Collector<? super T, A, R> collector);
Collector接口源码如下所示
java
public interface Collector<T, A, R> {
// 提供新容器Supplier
Supplier<A> supplier();
// 元素合并Consumer
BiConsumer<A, T> accumulator();
// 容器合并conbiner
BinaryOperator<A> combiner();
// 类型转换finisher,可以将A转成R
Function<A, R> finisher();
// collect
Set<Characteristics> characteristics();
// 底层创建CollectorImpl类
public static<T, R> Collector<T, R, R> of(Supplier<R> supplier,
BiConsumer<R, T> accumulator,
BinaryOperator<R> combiner,
Characteristics... characteristics) {
// ... 省略部分代码
return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, cs);
}
// 底层创建CollectorImpl类
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) {
// ... 省略部分代码
return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, finisher, cs);
}
// Collector特性
enum Characteristics {
// 并行流Collector标记
CONCURRENT,
// 集合操作是无序的
UNORDERED,
// 标识finisher,如果设置了,则从A到R的未检查强制转换必须成功。
IDENTITY_FINISH
}
}
我们将上面的代码通过Collector的方式改造,改造代码如下
java
public static void main(String[] args) {
// 自定义Collector
Collector<Integer, List<Integer>, List<Integer>> collector = Collector.of(ArrayList::new // 创建结果容器
, List::add // 将流中的对象添加到List容器中
, (list1, list2) -> {
list1.addAll(list2);
return list1;
}
);
// 通过自定义Collector收集result
List<Integer> result = Stream.of(1, 2, 3, 4, 5)
.collect(collector);
// 打印collect结果
ListUtil.printArray(result);
}
输出结果如下所示,方法一中的结果相同
Collectors详解
Collectors是Collector接口的不同实现,它包含多种终止操作,例如将元素收集到Collection中,将元素汇总。
下面截取了Collectors中的部分源码,Collectors的构造方法是私有的,因此我们在使用时不能直接构建Collectors。在Collectors并没有直接实现Collector接口,而是通过静态内部类CollectorImpl
实现了Collector
接口
java
public final class Collectors {
// ...
// 私有构造方法
private Collectors() { }
// 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;
}
}
// ...
}
将元素收集到容器(toCollection,toList,toSet)
Collectors提供了三种将流中的元素收集到容器中的方法
toCollection(Supplier<C> collectionFactory)
:将流中的元素收集到Collection中toList
:将流中的元素收集到List中toSet
:将流中的元素收集到Set中
java
// java.util.stream.Collectors源码
// toCollection将元素收集到入参Supplier提供的Collection容器中
public static <T, C extends Collection<T>>
Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
return new CollectorImpl<>(collectionFactory, Collection<T>::add,
(r1, r2) -> { r1.addAll(r2); return r1; },
CH_ID);
}
// toList,将元素收集到ArrayList中
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);
}
// toSet,将元素收集到HashSet中
public static <T>
Collector<T, ?, Set<T>> toSet() {
return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add,
(left, right) -> { left.addAll(right); return left; },
CH_UNORDERED_ID);
}
使用代码演示
java
// toCollection使用方法,如果supplier传入的是ArrayList::new,那么相当于是Collection.toList()
List<Integer> result1 = Stream.of(1, 2, 3, 4, 5).collect(Collectors.toCollection(ArrayList::new));
// toList使用方法
List<Integer> result2 = Stream.of(1, 2, 3, 4, 5).collect(Collectors.toList());
// toSet使用方法
Set<Integer> result3 = Stream.of(1, 2, 3, 4, 5).collect(Collectors.toSet());
将流中元素拼接成字符串(joining)
Collectors提供了三种将元素拼接成字符串的方法
joining()
:将流中的元素直接拼接成字符串joining(CharSequence delimiter)
:将流中的元素拼接成字符串,并用delimiter
分隔joining(CharSequence delimiter,CharSequence prefix,CharSequence suffix)
:将流中的元素拼接成字符串,并支持前缀,后缀和分隔符
java
// 利用StringBuilder进行拼接
public static Collector<CharSequence, ?, String> joining() {
return new CollectorImpl<CharSequence, StringBuilder, String>(
StringBuilder::new, StringBuilder::append,
(r1, r2) -> { r1.append(r2); return r1; },
StringBuilder::toString, CH_NOID);
}
// 前后缀默认为空
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter) {
return joining(delimiter, "", "");
}
// 底层使用StringJoiner进行拼接
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix) {
return new CollectorImpl<>(
() -> new StringJoiner(delimiter, prefix, suffix),
StringJoiner::add, StringJoiner::merge,
StringJoiner::toString, CH_NOID);
}
Stringjoiner
底层其实也是通过StringBuilder
拼接
使用代码演示
java
public static void main(String[] args) {
// 直接join
System.out.println(Stream.of("1", "2", "3", "4", "5").collect(Collectors.joining()));
// 使用,分隔
System.out.println(Stream.of("1", "2", "3", "4", "5").collect(Collectors.joining(",")));
// [前缀,]后缀,,分隔
System.out.println(Stream.of("1", "2", "3", "4", "5").collect(Collectors.joining(",","[","]")));
}
执行结果如下
将流中元素收集到Map中(toMap)
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
这个toMap()
方法包含两个参数,一个是流中的元素转换成map的key的mapper,另一个是将流中的元素转换成value的map
使用示例如下
java
// 将Person流转换成key是age,value是name的map
Map<Integer, String> result = Stream.of(new Person(18, "linshifu"), new Person(17, "Xiaozhang"))
.collect(Collectors.toMap(Person::getAge,Person::getName));
上面的toMap方法必须要保证流中的元素转成的key必须是唯一的,否则会抛出异常,例如
java
// 与上面例子相似,流中有两个年龄
Map<Integer, String> result = Stream.of(new Person(18, "linshifu"), new Person(17, "Xiaozhang"),new Person(18, "XiaoLi"))
.collect(Collectors.toMap(Person::getAge,Person::getName));
执行结果如下所示
如果想避免抛出异常,可以使用下面的方法
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
相比上面的toMap()
,这个方法中增加了BinaryOperator<U> mergeFunction
提供了两个元素合并的参数,例如
java
Map<Integer, String> result1 = Stream.of(new Person(18, "linshifu"),
new Person(17, "Xiaozhang"), new Person(18, "Xiaoli"))
.collect(Collectors.toMap(Person::getAge, Person::getName, (name1, name2) -> name1));
System.out.println("result1: "+result1);
// 如果key重复,则将name进行拼接,并用,分隔
Map<Integer, String> result2 = Stream.of(new Person(18, "linshifu"),
new Person(17, "Xiaozhang"), new Person(18, "Xiaoli"))
.collect(Collectors.toMap(Person::getAge, Person::getName, (name1, name2) -> name1+","+name2));
System.out.println("result2: "+result2);
执行过程没有报错,执行结果如下所示
上面的流转map默认是创建一个HashMap,如果我们想要自己提供Map容器,可以使用下面方法
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)
它增加了Supplier<M> mapSupplier
入参,可以自己提供一个Map容器,例如
java
// 使用LinkedHashMap作为收集容器
Map<Integer, String> result3 = Stream.of(new Person(18, "linshifu"),
new Person(17, "Xiaozhang"), new Person(18, "Xiaoli"))
.collect(Collectors.toMap(Person::getAge, Person::getName, (name1, name2) -> name1, LinkedHashMap::new));
System.out.println("result3: "+result3);
将流中元素分组(groupingBy)
Collctors中的groupingBy()
可以实现分组操作,将流中的元素进行分组,分组后会得到Map<K,D>
入参:
Function<? super T, ? extends K> classifier
:将元素分组FunctionSupplier<M> mapFactory
:产生返回Map的SupplierCollector<? super T, A, D> downstream
:聚合的Collector
java
// java.util.stream.Collectors#groupingBy
public static <T, K, D, A, M extends Map<K, D>>
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream)
使用例子如下
java
// 按照年龄分组,年龄作为key,将相同年龄的person收集到List中
Map<Integer, List<Person>> result1 = Stream.of(new Person(18, "linshifu"),
new Person(17, "Xiaozhang"), new Person(18, "Xiaoli"))
.collect(Collectors.groupingBy(Person::getAge, HashMap::new, Collectors.toList()));
System.out.println("result1:"+result1);
// 按照年龄分组,年龄作为key,将相同年龄的person收集到Set中
Map<Integer, Set<Person>> result2 = Stream.of(new Person(18, "linshifu"),
new Person(17, "Xiaozhang"), new Person(18, "Xiaoli"))
.collect(Collectors.groupingBy(Person::getAge, HashMap::new, Collectors.toSet()));
System.out.println("result2:"+result2);
执行结果如下
Collectors.groupingBy()与Collectors.toMap()对比
Collectors.toMap()适用于通过键(Map)收集到Value包含单个值
Collectors.groupingBy()适用于通过键(Map)收集到value包含多个值(List,Set)
Collectors还提供了另外两种groupingBy的重载方法
java
// 默认收集到HashMap中,我们只需要传入分组函数(classifier)和收集器(downstream)
public static <T, K, A, D>
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream) {
return groupingBy(classifier, HashMap::new, downstream);
}
// 默认收集到HashMap<K,List<V>>中,只需要传入分组函数(classifier)
public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier) {
return groupingBy(classifier, toList());
}
将流元素分区(partitionBy)
虽然在Collectors里的方法叫partitionBy
,但是只能将流中的元素分到Key时Boolean的Map中,Collectors提供了两个分区函数
java
public static <T>
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate)
入参是Predicate<? super T> predicate
,用于判断将流中的元素划分到true还是false分区
*
java
public static <T, D, A>
Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
Collector<? super T, A, D> downstream)
这个方法相比上面的方法增加了聚合的Collector(Collector<? super T, A, D> downstream
)
使用例子如下
java
// 按照年龄分组,年龄作为key,将相同年龄的person收集到List中
Map<Boolean, List<Person>> result1 = Stream.of(new Person(18, "linshifu"),
new Person(17, "Xiaozhang"), new Person(19, "Xiaoli"))
.collect(Collectors.partitioningBy(p -> p.getAge() % 2 == 0));
System.out.println(result1);
// 将流中的元素按照年龄分区,分区中的结果输出到Set中
Map<Boolean, Set<Person>> result2 = Stream.of(new Person(18, "linshifu"),
new Person(17, "Xiaozhang"), new Person(19, "Xiaoli"))
.collect(Collectors.partitioningBy(p -> p.getAge() % 2 == 0, Collectors.toSet()));
System.out.println(result2);
执行结果如下
求平均值(averagingInt,averagingLong,averagingDouble)
Collectors提供了三个求平均值的实现方法
java
public static <T> Collector<T, ?, Double>
averagingInt(ToIntFunction<? super T> mapper)
计算Int的平均值:入参为将流中的元素转为int的函数,返回Double
java
public static <T> Collector<T, ?, Double>
averagingLong(ToLongFunction<? super T> mapper)
计算Long的平均值:入参为将流中的元素转为Long的函数,返回Double
java
public static <T> Collector<T, ?, Double>
averagingDouble(ToDoubleFunction<? super T> mapper)
计算Double的平均值:入参为将流中的元素转为Long的函数,返回Double
使用例子如下
java
// 求流中所有人年龄的平均值
Double result = Stream.of(new Person(18, "linshifu"),
new Person(17, "Xiaozhang"), new Person(19, "Xiaoli"))
.collect(Collectors.averagingInt(Person::getAge));
System.out.println(result);
求统计值(summarizingInt,summarizingLong,summarizingDouble)
Collectors提供了三个求统计值的实现方法
java
public static <T>
Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper)
计算Int的统计值:入参为将流中的元素转为int的函数,返回IntSummaryStatistics
java
public static <T>
Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper)
计算Long的统计值:入参为将流中的元素转为Long的函数,返回LongSummaryStatistics
java
public static <T>
Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper)
计算Double的统计值:入参为将流中的元素转为Double的函数,返回DoubleSummaryStatistics
IntSummaryStatistics,LongSummaryStatistics,DoubleSummaryStatistics包含数量统计(count),汇总值(sum),最小值(min),最大值(max),平均值(average)
使用例子如下
java
// 求所有人年龄的统计值
IntSummaryStatistics result = Stream.of(new Person(18, "linshifu"),
new Person(17, "Xiaozhang"), new Person(19, "Xiaoli"))
.collect(Collectors.summarizingInt(Person::getAge));
System.out.println(result);
执行结果如下
求最大值、最小值(maxBy,minBy)
获取流中最小的元素
java
public static <T> Collector<T, ?, Optional<T>>
minBy(Comparator<? super T> comparator)
获取流中最大的元素
java
public static <T> Collector<T, ?, Optional<T>>
minBy(Comparator<? super T> comparator)
入参都是Comparator<? super T> comparator
,返回值是Optional<T>
使用例子如下
java
// 获取age最大的Person
Optional<Person> maxAgePerson = Stream.of(new Person(18, "linshifu"), new Person(17, "Xiaozhang"))
.collect(Collectors.maxBy(Comparator.comparing(Person::getAge)));
// 上面collect等价于.max(Comparator.comparing(Person::getAge));
System.out.println(maxAgePerson);
// 获取age最小的Person
Optional<Person> minAgePerson = Stream.of(new Person(18, "linshifu"), new Person(17, "Xiaozhang"))
.collect(Collectors.minBy(Comparator.comparing(Person::getAge)));
// 上面collect方法等价于.min(Comparator.comparing(Person::getAge));
System.out.println(minAgePerson);
归集到单个值(reducing)
Collectors提供的归集方法有下面三个
reducing(T identity, BinaryOperator<T> op)
第一个参数是归集初始值(identity
),第二个参数是归集函数BinaryOperator
reducing(BinaryOperator<T> op)
这个方法不需要传入初始值,执行时会取流中的第一个值作为初始值,只需要传入BinaryOperator
,由于没有给初始值,因此返回的值可能为空,它与上面方法的区别是会返回Optional<T>
reducing(U identity, Function<? super T, ? extends U> mapper,BinaryOperator<U> op)
这个方法相比第一个方法,增加了mapping函数作为第二个参数,将流中的每个元素映射成另一个元素
使用例子如下
java
// 计算1-10之和
Integer result1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.collect(Collectors.reducing(/*indentity*/0,/*BinaryOperator*/(a, b) -> a + b));
Optional<Integer> result2 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.collect(Collectors.reducing(/*BinaryOperator*/(a, b) -> a + b));
// 计算流中所有Person的年龄之和
Integer age = Stream.of(new Person(18, "linshifu"), new Person(17, "Xiaozhang"))
.collect(Collectors.reducing(0, /*map年龄*/Person::getAge, (a, b) -> a + b));
收集完后再执行其他操作(collectingAndThen)
收集完之后再执行finisher函数
collectingAndThen(Collector<T,A,R> downstream,Function<R,RR> finisher)
使用例子如下
java
// 取出流中年龄最小的两个Person,并返回List
List<Person> result = Stream.of(new Person(18, "linshifu"),
new Person(17, "Xiaozhang"), new Person(19, "Xiaoli"))
.collect(Collectors.collectingAndThen(Collectors.toList(), personList -> {
personList.sort(Comparator.comparingInt(Person::getAge));
return personList.subList(0, 2);
}));
System.out.println(result);
执行结果如下