Java Stream – Collectors类和collect()方法

时间:2020-01-09 10:35:15  来源:igfitidea点击:

使用Java流时,大多数情况下,我们会使用Collection作为流的源,但是我们也可以相反地进行操作,即从Stream获取Collection。为此,我们可以在Java Stream API中使用collect()方法。在此请注意,collect()方法对该流的元素执行可变的约简操作,该操作返回可变的结果容器。该可变结果容器可以是Collection类,例如ArrayList,HashSet或者StringBuilder等。

Java Stream中的collect()方法

collect()方法是一种终端操作,有两个重载的collect()方法。

1 <R,A> R collect(Collector <?super T,A,R>收集器)–使用收集器对该流的元素执行可变还原操作。

在方法类型中,参数为

T归约运算的输入元素类型

A –收集器的中间累积类型

R –结果类型

2- <R> R收集(Supplier <R>供应商,BiConsumer <R ,? super T>累加器,BiConsumer <R,R>组合器)–对此流的元素执行可变的归约运算。

该方法的参数如下:

可变结果容器的类型

providera函数,用于创建新的可变结果容器。它是供应商功能接口的一个实例。

累加的,关联的,无干扰的,无状态的功能,必须将元素折叠到结果容器中。它是BiConsumer功能接口的一个实例。

Combineran关联的,无干扰的,无状态的函数,它接受两个部分结果容器并将其合并,这些容器必须与累加器功能兼容。它是BiConsumer功能接口的一个实例。

Java Stream中的Collector类

在第一个collect()方法中,我们可以看到该参数的类型为Collector,它是java.util.stream包中的接口,并且定义了许多方法。

可以自己使用Collectors类,而不是自己实现这些方法,它是Collector的实现,并提供了许多实用程序简化方法,例如将元素累积到集合中,根据各种标准对元素进行汇总等。

Java收集器示例

在本节中,我们将看到一些将预定义的收集器与collect()方法一起使用的示例。

大多数示例使用Employee类的对象,这些对象具有字段name,dept,salary。

public class Employee {
  private String name;
  private String dept;
  private int salary;

  Employee(String name, String dept, int salary){
    this.name = name;
    this.dept = dept;
    this.salary = salary;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public int getSalary() {
    return salary;
  }
  public void setSalary(int salary) {
    this.salary = salary;
  }
  public String getDept() {
    return dept;
  }
  public void setDept(String dept) {
    this.dept = dept;
  }
}

1.收集到列表中– Collectors.toList()

将所有员工姓名收集到列表中。

List<Employee> employeeList = new ArrayList<>(); 

employeeList.add(new Employee("Hyman", "Finance", 5500)); 
employeeList.add(new Employee("Lisa", "Finance", 5600)); 
employeeList.add(new Employee("Scott", "Finance", 7000));
employeeList.add(new Employee("Nikita", "IT", 4500));
employeeList.add(new Employee("Tony", "IT", 8000)); 

List<String> names = employeeList.stream()
                                 .map(e -> e.getName())
                                 .collect(Collectors.toList());

names.forEach(System.out::println);

输出:

Hyman
Lisa
Scott
Nikita
Tony

2.收集到集合中– Collectors.toSet()

Set<String> names = employeeList.stream()
                                .map(Employee::getName)
                                .collect(Collectors.toSet());

names.forEach(System.out::println);

输出:

Tony
Nikita
Hyman
Lisa
Scott

3.收集到TreeSet中– Collectors.toCollection()

将所有员工姓名收集到TreeSet中以按顺序获取姓名。

Set<String> names = employeeList.stream()
                                .map(Employee::getName)
                                .collect(Collectors.toCollection(TreeSet::new));

names.forEach(System.out::println);

输出:

Hyman
Lisa
Nikita
Scott
Tony

4.收集到地图中– Collectors.toMap()

要使用toMap()方法将元素累积到Map中,我们需要提供两个函数

keyMapper此函数用于通过将功能应用于输入元素来获取键。

valueMapper此函数用于通过将函数应用于输入元素来获取值。

Map<String, Integer> names = employeeList.stream()
                                         .collect(Collectors.toMap(Employee::getName, Employee::getSalary));
							
names.entrySet().forEach(es->{System.out.println("Key- " + es.getKey() + " Value- " +  es.getValue());});

输出:

Key- Tony Value- 8000
Key- Nikita Value- 4500
Key- Hyman Value- 5500
Key- Lisa Value- 5600
Key- Scott Value- 7000

5.将元素转换为字符串并将其连接起来

如果要将员工姓名显示为逗号分隔的字符串。

String names = employeeList.stream()
                           .map(Employee::getName)
                           .collect(Collectors.joining(", "));

System.out.println(names);

输出:

Hyman, Lisa, Scott, Nikita, Tony

6.计算sumCollectors.summingInt()

支付给员工的工资总和。

int totalSalary = employeeList.stream()
                              .collect(Collectors.summingInt(Employee::getSalary));

System.out.println("Total salary paid to employees per month- " + totalSalary);

输出:

Total salary paid to employees per month- 30600

7.按fieldCollectors.groupingBy()分组

如果要按部门对员工进行分组,则返回值为Map。

Map<String, List<Employee>> names = employeeList.stream()
                                                            .collect(Collectors.groupingBy(Employee::getDept));

names.entrySet().forEach(es->{System.out.println("Key- " + es.getKey());
                        System.out.println("Values");
                        es.getValue().forEach(e->System.out.println(e.getName()));});

输出:

Key- Finance
Values
Hyman
Lisa
Scott
Key- IT
Values
Nikita
Tony
  1. Collectors.partitioningBy

返回一个收集器,该收集器根据谓词对输入元素进行分区,并将它们组织成Map <Boolean,
列出<T >>。返回的Map始终包含false和true键的映射。对于真键,匹配给定谓词的那些元素将被映射,而不匹配给定谓词的元素将被映射到错误键下。
将员工分成薪水大于等于7000且小于等于7000的组。

Map<Boolean, List<Employee>> names = employeeList.stream()
                                                 .collect(Collectors.partitioningBy(e -> e.getSalary() >= 7000));

输出:

Key- false
Values
Hyman
Lisa
Nikita
Key- true
Values
Scott
Tony
  1. Collectors.teeing

返回一个由两个下游收集器组成的收集器。传递给结果收集器的每个元素都由两个下游收集器处理,然后使用指定的合并功能将它们的结果合并为最终结果。 JDK 12中添加了此方法。

使用Collectors.teeing函数获取列表中元素的数量和总和。

List<Integer> listOfNumbers = Arrays.asList(11, 10, 9, 99, 98);
 List<String> list = listOfNumbers.stream().collect(Collectors.teeing(Collectors.counting(), Collectors.summingInt(n->Integer.valueOf(n.toString())), 
		 (a, s)->{List<String> l = new ArrayList<>();
		 		l.add(a.toString());
		 		l.add(s.toString());
		 		return l;}));
 list.forEach(System.out::println);

输出:

5
227
  1. Collectors类中的摘要统计方法
    有三种方法summarizingInt,summarizingLong和summarizingDouble返回结果值的摘要统计信息。

获取有关员工工资的摘要统计信息。

IntSummaryStatistics stats = employeeList.stream().collect(Collectors.summarizingInt(Employee::getSalary));
System.out.println("Sum of salaries - " + stats.getSum());
System.out.println("Average of salaries " + stats.getAverage());
System.out.println("Max salary " + stats.getMax());
System.out.println("Min salary " + stats.getMin());

输出:

Sum of salaries - 30600
Average of salaries 6120.0
Max salary 8000
Min salary 4500

在Combiner中使用collect方法

这种形式的collect方法需要三个功能:供应商功能,用于构造结果容器的新实例;累加器功能,用于将输入元素合并到结果容器中;以及合并功能,用于将一个结果容器的内容合并到另一个结果容器中。

例如,将流中元素的整数表示形式收集到ArrayList中。

List<Integer> numbers = Stream.of(1, 2, 3, 4, 5).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
numbers.forEach(System.out::println);

输出:

1
2
3
4
5

将所有员工姓名作为连接字符串,其中值之间用逗号分隔。

String concat = employeeList.stream().map(Employee::getName).collect( () -> new StringJoiner(",", "", ""), StringJoiner::add, StringJoiner::merge).toString();					   
System.out.println("Employee Names- " + concat);

输出:

Employee Names- Hyman,Lisa,Scott,Nikita,Tony