归约与汇总

  • 归约(reduce):将Stream流中元素转换成一个
  • 汇总(collect):将Stream流中元素转换成一个容器(集合类等的容易)

归约

将Stream流中元素转换成一个,只返回一个值!!!

1
2
3
4
5
6
7
8
9
10
Stream<Integer> integerStream = Lists.newArrayList(1, 2, 3).stream();

// 求最大值
integerStream.mapToInt(Integer::intValue).max();

// 求最小值
integerStream.mapToInt(Integer::intValue).min();

// 求和
integerStream.mapToInt(Integer::intValue).sum();

归约操作理解

例如:一个Stream流进行累加

image-20201029113847880

reduce接口参数

以reduce最复杂的接口参数为例

1
2
3
4
5
6
7
8
9
10
11
/**
* 归约操作接口定义
* @param identity - 初始值
* @param accumulator - 计算逻辑
* @param combiner - 并行执行时多个部分结果的合并方式
* @param <U> - 元素类型
* @return
*/
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);

结合图查看接口做了什么(分治思想)

image-20201029115956187

实战案例:自定义归约

根据一批订单信息,计算平均商品价格

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
package com.stream.shopping;

import cn.hutool.json.JSONUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;

/**
* 归约与汇总操作
*
* @author jinglv
* @date 2021/7/30 1:48 下午
*/
public class ReduceAndCollectTest {

@Test
void reduceTest() {
/*
* 定义订单对象
*/
@Data
@AllArgsConstructor
class Order {
/**
* 订单编号
*/
private Integer id;
/**
* 商品数量
*/
private Integer productCount;
/**
* 消费总金额
*/
private Double totalAmount;
}
// 准备数据
List<Order> list = new ArrayList<>();
list.add(new Order(1, 2, 25.12));
list.add(new Order(2, 5, 257.23));
list.add(new Order(3, 3, 25512.12));

/*
* 传统的方式:
* 1.计算商品数量
* 2.计算消费总金额
*/

// 汇总商品数量和总金额
Order order = list.stream()
// 启用流的并行模式, 若不添加parallel(),则不会执行并行中的内容
.parallel()
.reduce(
// 参数一:初始化值
new Order(0, 0, 0.0),
// 参数二:Stream中两个元素的计算逻辑
(Order order1, Order order2) -> {
System.out.println("执行计算逻辑方法");
int productCount = order2.getProductCount() + order1.getProductCount();
double totalAmount = order1.getTotalAmount() + order2.getTotalAmount();
return new Order(0, productCount, totalAmount);
},
// 参数三:并行情况下,多个并行结果如何合并
(Order order1, Order order2) -> {
System.out.println("执行合并方法");
int productCount = order1.getProductCount() + order2.getProductCount();
double totalAmount = order1.getTotalAmount() + order2.getTotalAmount();
return new Order(0, productCount, totalAmount);
}
);
System.out.println(JSONUtil.parse(order));
}
}

执行结果

image-20210730141838304

汇总

将Stream流中元素转换成一个容器

1
2
3
4
5
6
7
8
9
10
Stream<Integer> integerStream = Lists.newArrayList(1, 2, 3).stream();

// 转换成List集合
List<Integer> list = integerStream.collect(Collectors.toList());

// 按奇偶分区
Map<Boolean, List<Integer>> partitions = integerStream.collect(Collectors.partitioningBy(item -> item % 2 == 0));

// 按元素分组
Map<Boolean, List<Integer>> groups = integerStream.collect(Collectors.groupingBy(item -> item));

collect接口参数

1
2
3
4
5
6
7
8
9
10
11
/**
* 汇总操作接口定义
* @param supplier - 初始化结果容器
* @param accumulator - 添加元素到结果容器逻辑
* @param combiner - 并行执行时多个部分结果容器的合并方式
* @param <R> - 元素类型
* @return
*/
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);

实战案例:自定义收集

根据一批订单信息,计算每个用户的平均商品价格

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
    @Test
void collectTest() {
/*
* 订单对象
*/
@Data
@AllArgsConstructor
class Order {
/**
* 订单编号
*/
private Integer id;
/**
* 用户账号
*/
private String account;
/**
* 商品数量
*/
private Integer productCount;
/**
* 消费总金额
*/
private Double totalAmount;
}

// 准备数据
List<Order> list = new ArrayList<>();
list.add(new Order(1, "zhangsan", 2, 25.12));
list.add(new Order(2, "zhangsan", 5, 257.23));
list.add(new Order(3, "lisi", 3, 25512.12));

// Map<用户账号,订单(商品数量和金额)>
// 注意:stream().collect(),Stream直接调用collect是不会开启并行模式的,加入parallel才会开启并行模式
HashMap<String, Order> orderHashMap = list.stream()
// 启用流的并行模式, 若不添加parallel(),则不会执行并行中的内容
.parallel()
.collect(
() -> {
System.out.println("执行初始化容器操作");
return new HashMap<String, Order>();
},
(HashMap<String, Order> map, Order newOrder) -> {
System.out.println("执行新元素添加到容器操作");
// 1. 新元素的account已经在map中存在了
// 2. 新元素的account已经在map中不存在了
String account = newOrder.getAccount();
// 如果此账号已存在,将新订单的数据累加上
if (map.containsKey(account)) {
Order order = map.get(account);
order.setProductCount(newOrder.getProductCount() + order.getProductCount());
order.setTotalAmount(newOrder.getTotalAmount() + order.getTotalAmount());
} else {
// 如果不存在,直接将新订单存入map
map.put(account, newOrder);
}
},
(HashMap<String, Order> map1, HashMap<String, Order> map2) -> {
System.out.println("执行并行结果合并操作");
map2.forEach((key, value) -> {
// merge:如果从map1中发现了这个key,那么就把这个value作为参数,map1中的value也作为参数(order1, order2)进行传递
// 如果这个key没有在map1中出现,那直接将value push到map1中
map1.merge(key, value, (order1, order2) -> {
// 注意:一定要用map1做合并,因为最后collect返回是map1
return new Order(0, key,
order1.getProductCount() + order2.getProductCount(),
order1.getTotalAmount() + order2.getTotalAmount());
});
});
}
);
System.out.println(JSONUtil.parse(orderHashMap));
}
}

执行结果

image-20210802135227135

问题:多次初始化容器map会不会存在内容泄露风险

解答:方法里面的局部变量,方法执行完,没有外部引用了,自然会被标记为可回收,等待GC回收