Java8 - 定制归一化收集器(reducing)得到自定义结果集

reducing简介

reducing 是一个收集器(操作),从字面意义上可以理解为“减少操作”:输入多个元素,在一定的操作后,元素减少。

reducing 有多个重载方法,其中一个方法如下:

1
public static <T> Collector<T,?,Optional<T>> reducing(BinaryOperator<T> op)

以上方法,JDK对其的描述是:

Returns a Collector which performs a reduction of its input elements under a specified BinaryOperator. The result is described as an Optional. (返回一个收集器,该收集器在指定的二进制操作符下执行其输入元素的减少。结果被描述为可选的 <T>。)

reducing的应用

reducing 是一个非常有用的收集器,可以用在多层流、下游数据分组或分区等场合。

下面是一个例子:
给定一组 Person 对象,每个 Person 都有 city(所在城市)和 height(身高)属性,编写一个程序,统计出每个不同的城市最大、最小身高值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import java.util.*;
import java.util.function.BinaryOperator;

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.reducing;

/**
* ReducingDemo
*
* @author Zebe
*/
public class ReducingDemo {

/**
* 运行入口
*
* @param args 运行参数
*/
public static void main(String[] args) {
List<Person> personList = getPersonList(1000000);
functionStyle(personList);
normalStyle(personList);
}

/**
* 函数式编程风格(3行处理代码)
* @param personList Person 列表
*/
private static void functionStyle(List<Person> personList) {
long start = System.currentTimeMillis();
// 创建一个比较器,取名为 byHeight (通过高度来比较)
Comparator<Person> byHeight = Comparator.comparingInt(Person::getHeight);
// 创建一个归一收集器
Map<City, Optional<Person>> tallestByCity = personList.stream().
collect(groupingBy(Person::getCity, reducing(BinaryOperator.maxBy(byHeight))));
long usedTime = System.currentTimeMillis() - start;
printResult("函数式编程风格", personList.size(), usedTime, tallestByCity);
}

/**
* 普通编程风格(20行处理代码)
* @param personList Person 列表
*/
private static void normalStyle(List<Person> personList) {
long start = System.currentTimeMillis();
// 创建一个结果集
Map<City, Optional<Person>> tallestByCity = new HashMap<>();
// 第一步:找出所有的不同城市
Set<City> cityList = new HashSet<>();
for (Person person : personList) {
if (!cityList.contains(person.getCity())) {
cityList.add(person.getCity());
}
}
// 第二部,遍历所有城市,遍历所有人找出每个城市的最大身高
for (City city : cityList) {
int maxHeight = 0;
Person tempPerson = null;
for (Person person : personList) {
if (person.getCity().equals(city)) {
if (person.getHeight() > maxHeight) {
maxHeight = person.getHeight();
tempPerson = person;
}
}
}
tallestByCity.put(city, Optional.ofNullable(tempPerson));
}
long usedTime = System.currentTimeMillis() - start;
printResult("普通编程风格", personList.size(), usedTime, tallestByCity);
}

/**
* 获取Person列表
* @param numbers 要获取的数量
* @return 返回指定数量的 Person 列表
*/
private static List<Person> getPersonList(int numbers) {
// 创建城市
final City cityChengDu = new City("成都");
final City cityNewYork = new City("纽约");
List<Person> people = new ArrayList<>();
// 创建指定数量的Person,并指定不同的城市和相对固定的身高值
for (int i = 0; i < numbers; i++) {
if (i % 2 == 0) {
// 成都最大身高185
people.add(new Person(cityChengDu, 185));
} else if (i % 3 == 0) {
people.add(new Person(cityChengDu, 170));
} else if (i % 5 == 0) {
// 成都最小身高160
people.add(new Person(cityChengDu, 160));
} else if (i % 7 == 0) {
// 纽约最大身高200
people.add(new Person(cityNewYork, 200));
} else if (i % 9 == 0) {
people.add(new Person(cityNewYork, 185));
} else if (i % 11 == 0) {
// 纽约最小身高165
people.add(new Person(cityNewYork, 165));
} else {
// 默认添加纽约最小身高165
people.add(new Person(cityNewYork, 165));
}
}
return people;
}

/**
* 输出结果
* @param styleName 风格名称
* @param totalPerson 总人数
* @param usedTime 计算耗时
* @param tallestByCity 统计好最大身高的城市分组MAP
*/
private static void printResult(String styleName, long totalPerson, long usedTime, Map<City, Optional<Person>> tallestByCity) {
System.out.println("\n" + styleName + ":计算 " + totalPerson + " 个人所在不同城市最大身高的结果如下:(耗时 " + usedTime + " ms)");
tallestByCity.forEach((city, person) -> {
person.ifPresent(p -> System.out.println(city.getName() + " -> " + p.getHeight()));
});
}

}

Person类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* Person
*
* @author Zebe
*/
public class Person {

/**
* 所在城市
*/
private City city;

/**
* 身高
*/
private int height;

/**
* 构造器
* @param city 所在城市
* @param height 身高
*/
public Person(City city, int height) {
this.city = city;
this.height = height;
}

public City getCity() {
return city;
}

public void setCity(City city) {
this.city = city;
}

public int getHeight() {
return height;
}

public void setHeight(int height) {
this.height = height;
}
}

City类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* City
*
* @author Zebe
*/
public class City {

/**
* 城市名
*/
private String name;

/**
* 构造器
* @param name 城市名
*/
public City(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

:以上代码如果要计算最小值、平均值,将 maxBy 换成 minBy 就可以了。

输出结果

程序输出结果如下:

1
2
3
4
5
6
7
函数式编程风格:计算 1000000 个人所在不同城市最大身高的结果如下:(耗时 149 ms)
成都 -> 185
纽约 -> 200

普通编程风格:计算 1000000 个人所在不同城市最大身高的结果如下:(耗时 82 ms)
成都 -> 185
纽约 -> 200

可以看出,函数式编程的效率不一定会比普通编程效率更高,甚至相对要慢一点,但是,函数式编程的好处在于:

  • 把参数作为一个函数,而不是值,实现了只有在需要的时候才计算(惰性求值)。
  • 使用 lambda 表达式能够简化程序表达的含义,使程序更简洁明了。