7 Collector 接口详解(collect 收集器、Collectors)*****************
-
collect: 收集器
-
Collector 作为 collect 方法的参数
-
Collector 本身是一个接口,它是一个可变的汇聚操作,作用是将输入元素累积到一个可变的结果容器中(如ArrayList 是一个可变容器);(可选操作)它会在所有元素都处理完毕后,将累积的结果转换为一个最终的表示。它支持串行和并行两者方式。
-
Collectors 本身提供了关于 Collector 的常见汇聚实现,Collectors 本身是一个工厂.
-
Collector 接受四个函数进行共同工作,
- 第一个参数(supplier 新容器) :创建一个新的结果容器(Supplier supplier 不接受参数返回一个结果)
- 第二个参数(accumulator 累加器):将新的数据元素合并到一个结果容器中(BiConsumer accumulator 将一个值折叠到一个可变的结果容器中,其中 BiConsumer 函数式是用来接收两个参数,不返回值)
- 第三个参数(combiner 合并器 并发时用) :将两个部分结果容器合并成一个。可能将状态从一个参数折叠成另一个并且返回它,也可能返回一个新的容器(BinaryOperator combiner 接受两个同类型的参数,返回同类型的结果)
- 第四个参数(finisher 完成其):执行一个可选的最终转换,执行最终的转换,从中间累积的类型,转换成最终的类型(Function finisher)
-
为了确保串行和并行流操作结果的等价性,Collector函数需要满足两个条件:ideentity(同一性) 与 associativity(结合性)
-
同一性必须满足部分结果与空容器结合时等于部分结果:a ==combiner.apply(a, supplier.get() ); (其中a为部分累积结果) supplier.get() 获取的是空集合
(List<String> list1, List<String> list2 ) -> {list1.addAll(list2);return list1}
-
结合性:分割计算必须得到一个等价的结果
// t1 t2 是两个输入元素, r1 r2是两个执行结果 A a1 = supplier.get();// 得到空的结果容器 a1 accumulator.accept(a1, t1);// a1 是每一次累加之后的中间结果,t1 是带处理的下一个元素 accumulator.accept(a1, t2); R r1 = finisher.apply(a1); // result without splitting //分割计算 A a2 = supplier.get(); accumulator.accept(a2, t1); A a3 = supplier.get(); accumulator.accept(a3, t2); R r2 = finisher.apply(combiner.apply(a2, a3)); // result with splitting
-
对于无序的收集器,如果两个累积的结果是等价的判断是 finisher.apply(a1).equals(finisher.apply(a2)).对于无序的收集器,等同只会考虑包含相同的元素,忽略顺序。
combiner 函数 ,有4个线程同时去执行,那么久会生成四个部分结果,combine人函数的作用就是将四个部分结果合成一个。
1,2,3,4 四个部分结果,
返回一个新的容器:
1,2 -> 5
5,3 -> 6
6,4 -> 7
折叠成另一个并返回:
1, 2 -> 1
1,2,3,4 -> 1 就像是 flatMap 将1,2,3,4 平铺成一个stream
reduce 和 collect 两者很像,区别在于reduce结果集是个不可变的容器,而collect 是可变的容器,能用reduce实现的collect也可以实现,能用collect实现的 reduce 也可以,但是有一点非常重要,在并发过程中,不能使用reduce转collect会错乱
7.1实现Collector注意事项:
Collector<T, A, R>: T 是流中每个元素的类型。A是可变的累积类型即集合的类型。R: 表示汇聚结果的类型
Supplier<A> supplier(); // 结果容器的类型
BiConsumer<A, T> accumulator(); // A 是每次中间操作的类型,T是下一次待操作元素的类型
BinaryOperator<A> combiner(); // 合并两个部分结果,A类型
Function<A, R> finisher(); // 结果容器类型,最终给用户操作的类型 一般A是泛型(list),R是A的具体类型(list<String>)
创建一个收集器TreeSet:
Collector<Widget, ?, TreeSet<Widget>> intoSet = Collector.of(TreeSet::new, TreeSet::add, (left, right) -> { left.addAll(right); return left; });
7.2 collector 汇聚方式:
R container = collector.supplier().get();
* for (T t : data)
* collector.accumulator().accept(container, t);
* return collector.finisher().apply(container);
7.3 collector 嵌套使用
创建一个收集者计算员工流的薪酬总和:
Collector<Employee, ?, Integer> summingSalaries = Collectors.summingInt(Employee::getSalary))
如果我们想创建一个收集器来按部门列出工资总额,我们可以重复使用“工资总和”逻辑 Collectors.groupingBy(Function, Collector)
Collector<Employee, ?, Map<Department, Integer>> summingSalariesByDept = Collectors.groupingBy(Employee::getDepartment, summingSalaries);
7.4 Collectors 收集器工厂
Collectors 工厂,向开发者提供常见的收集器。 静态内部类 collectorImpl实现 collector 接口。
源码例子:
// Accumulate names into a List
List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());
// Accumulate names into a TreeSet
Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));
// Convert elements to strings and concatenate them, separated by commas
String joined = things.stream().map(Object::toString) .collect(Collectors.joining(", "));
// Compute sum of salaries of employee
int total = employees.stream() .collect(Collectors.summingInt(Employee::getSalary)));
// Group employees by department
Map<Department, List<Employee>> byDept = employees.stream().collect(Collectors.groupingBy(Employee::getDepartment));
// Compute sum of salaries by department
Map<Department, Integer> totalByDept = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment,Collectors.summingInt(Employee::getSalary)));
// Partition students into passing and failing
Map<Boolean, List<Student>> passingFailing = students.stream().collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
通过事例代码演示下,常用功能:
package cn.zxhysy.jdk8.stream2;
import java.util.*;
import java.util.stream.Collectors;
public class streamTest1 {
public static void main(String[] args) {
Student student1 = new Student("zhangsan",80);
Student student2 = new Student("lisi",90);
Student student3 = new Student("wangwu",100);
Student student4 = new Student("zhaoliu",90);
Student student5 = new Student("zhaoliu", 90);
List<Student> students = Arrays.asList(student1, student2, student3, student4,student5);
// stream 中,一个问题提供了多种解决方案,提倡越具体的方式越好,通过方式和具体方式都能解决,选择具体方式
students.stream()
.collect(Collectors.minBy(Comparator.comparingInt(Student::getScore)))
.ifPresent(System.out::println);
students.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Student::getScore)))
.ifPresent(System.out::println);
System.out.println(students.stream().collect(Collectors.averagingInt(Student::getScore)));
System.out.println(students.stream().collect(Collectors.summingInt(Student::getScore)));
IntSummaryStatistics intSummaryStatistics = students.stream().collect(Collectors.summarizingInt(Student::getScore));
System.out.println(intSummaryStatistics);
System.out.println("------------------");
System.out.println(students.stream().map(Student::getName).collect(Collectors.joining()));
System.out.println(students.stream().map(Student::getName).collect(Collectors.joining(", ")));
System.out.println(students.stream().map(Student::getName).collect(Collectors.joining(", ","<begin>","<end>")));
System.out.println("------------------");
// 先根据分数分组,再根据名字分组
Map<Integer, Map<String, List<Student>>> collect = students.stream().collect(Collectors.groupingBy(Student::getScore, Collectors.groupingBy(Student::getName)));
System.out.println(collect);
System.out.println("------------------");
// 分区,找出大于80和小于等于80的
Map<Boolean, List<Student>> map = students.stream().
collect(Collectors.partitioningBy(student -> student.getScore() > 80));
System.out.println(map);
System.out.println("------------------");
// 大于80分区,大于90分区
Map<Boolean, Map<Boolean, List<Student>>> map2 = students.stream()
.collect(Collectors.partitioningBy(student -> student.getScore() > 80,
Collectors.partitioningBy(student -> student.getScore() > 90)));
System.out.println(map2);
System.out.println("-------------------");
Map<Boolean, Long> map1 = students.stream()
.collect(Collectors.partitioningBy(student -> student.getScore() > 80,
Collectors.counting()));
System.out.println(map1);
System.out.println("-------------------");
Map<String, Student> map3 = students.stream()
.collect(Collectors
.groupingBy(Student::getName,
// 收集之后,返回Optional中的元素(minBy返回的是Optional,但既然有分组,那肯定有元素,我们就不需要判断Optional中是否有元素,手动将Optional拆开)
Collectors.collectingAndThen(
Collectors.minBy(Comparator.comparingInt(Student::getScore)),
Optional::get)));
}
}