WorkProj
内容管理
- Stream
- 中间操作 (流不会关闭)
- 终端操作 (关闭流), 返回值
- allMatch(x -> x boolean表达式) 当stream中所有元素满足条件返回true
- anyMatch(x -> x boolean表达式) 当stream中一个元素满足就返回true
- noneMatch(x -> x boolean表达式) 全部不满足返回true
- count() 统计流中元素个数
- findFirst() 返回流中第一个元素
- findAny() 返回流中随机一个元素
- max/min () 返回流中的最大最小值
- sum() 求和
- foreach() 遍历
- reduce 将流中的元素组合 ⭐ 可替代max、min、sum等
- collect 返回集合 collect策略有很多 ---> ⭐ 可执行sum、average...等
- 类型归纳 --- 转为集合容器 toXXX
- joining 将元素用某规则连接起来
- collectingAndThen 先对结果进行归纳,之后再进行Function函数处理结果
- groupingBy 按照条件对结果分组
- counting 对收集的结果求和
- mapping(mapper, downSream) 在Collect中进行隐射
- maxBy(comparator)/minBy
- summingInt/Double/Long(元素 -- 可用Lambda)
- summarizingDouble/Int/Long⭐ 统计量: 包括总数 和 max/min 平均值
- reducing ⭐ (T, T) -> T 元素两两比较淘汰一个(一轮轮reduce)
- workProj中应用
本文introduce 对于Stream的使用
在关系型数据库中处理数据还是很easy的,使用group by , order by等关键字可以方便查出各种数据
但是很多场景下数据不是在数据库中处理, 而是直接拎到内存中处理, 这个时候Stream就可以发挥巨大作用【相较于传统的for遍历】
比如java的dynamicSqlBuilder, 动态SQL查询出动态数据库表中的数据【所有都是动态的】, 这里的返回值就是List<Map<String, Object>>
要对List<Map<String,Object>>进行处理, 通过Stream的方式就是最快的
流stream是支持数据处理操作的源生成的元素序列, 源可以是数组、文件、集合、函数; Stream不是集合,不是数据结构,主要目的就是计算
Stream
Stream创建方式一共有5种, 集合、数组、of值、文件、函数iterator无限流
- 最常见的就是通过集合Collection生成
Stream stream = distinctMap.values().stream
除此之外,也可以通过数组Arrays.sttream(数组)、 Stream.of(值1, 值2…)、Files.lines(文件)、 Stream.iterator(xxx)无限流 | Stream.generator(xxx)无限流
流Stream的操作分为两种:
- 中间操作: 一个流后面可以跟随0或者多个中间操作,
打开流做出某种程度的映射
, 操作是惰性化的,没有真正开始流的遍历, 常见的map、filter都是 - 终端操作: 一个流只能有一个终端操作,
进行流的遍历
,操作执行后,流就关闭了,不能再操作,因此一个流只能遍历一次,如collect、count
中间操作 (流不会关闭)
中间操作可以为 0 ~ 多个, 一定程度的映射和操作
比如List list = Arrays.asList(1,1,2,3,4,5,6);
filter(x -> x boolean表达式) 筛选返回值为true的
list.stream().filter(item -> item > 3)
//这里的item代表的就是流中的每一个元素,可以使用Lambda表达式
//4,5,6
distinct() 去重
list.stream().distinct()
//结果1,2,3,4,5,6
limit(X) 返回前x个元素
这个和数据库limit类似
list.stream().limit(3)
//1,1,2
skip(x) 跳过流中元素
和limit相反,跳过前X个元素
list.stream().skip(3)
//3,4,5,6
map(x操作) 对每一个元素映射操作
操作位置可以使用Lambda或者函数引用
list.stream().map(x -> x + 1)
//2,2,3,4,5,6,7
mapToInt() 将对象流转为数值流 Integer --> int
flatMap(x操作) 将一个流中每一个值转为流 【针对对象为集合/array】合并集合
这个和Map的不同主要就是针对List<List<>>的情况, 一层流只能得到List, 要想直接得到最内层对象,就只能flatMap, 会将所有的List全转化为流
List<List《Stream>> strList = [[“java,python”], [“cfeng”,“cshen”]]
strList.stream()
.flatMap(item -> item.Stream())
sorted(Comparator.comparing(字段)) 将stream中元素排序
这个sorted中可以给出字段,就是按照默认的顺序排序,加上.reversed()
就可以倒序,当然也可以使用Lambda表达式进行定制的排序
list.stream.sorted(Comparator.comparing(Book:: getPublishTime).reversed())
终端操作 (关闭流), 返回值
终端操作只能一个, 会对流进行遍历(和requestBody一样,只能一次性)
allMatch(x -> x boolean表达式) 当stream中所有元素满足条件返回true
if(list.stream.allMatch(item -> item > 3))
//这里false,因为1,1,2,3几个元素不满足
anyMatch(x -> x boolean表达式) 当stream中一个元素满足就返回true
if(list.stream.anyMatch(item -> item > 5))
//true,因为6这个元素满足
noneMatch(x -> x boolean表达式) 全部不满足返回true
if(list.stream.noneMatch(item -> item > 5))
//false,因为6这个元素满足
count() 统计流中元素个数
list.stream().count()
//个数7
findFirst() 返回流中第一个元素
list.stream().findFirst()
//1
findAny() 返回流中随机一个元素
list.stream().findAny()
//4 , 随机的
max/min () 返回流中的最大最小值
list.stream().min(Integer::CompareTo)
list.stream().min()
//1
sum() 求和
list.stream().sum() //可以用lambda统计元素, 比如str.length求和
//1 + 1 + 2 + 3 + 4 + 5 + 6
foreach() 遍历
list.stream().foreach(System::println);
//1 /n 1 .....
reduce 将流中的元素组合 ⭐ 可替代max、min、sum等
(T,T) -> T, 不断reduce, 两两比较; 其实max、min、sum都是reduce的特例,一个是两两比大小,一个是两两求和
其中的操作可以用迭代的思想看就是 一次迭代【通用计算方式】, 之后程序内部就会按照公式不断两两操作得到最后的结果
reduce可以实现从Stream中生成一个值,不是随意的,根据指定的模型
-
reduce(BinaryOperator<T》 accumulator)
参数列表为一个函数式接口
public interface BinaryOperator<T> extends BiFunction<T,T,T> { public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) <= 0 ? a : b; } public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) >= 0 ? a : b; } } @FunctionalInterface public interface BiFunction<T, U, R> { R apply(T t, U u);//接收两个参数 t 和 u, 返回 R }
可以看到就是接受T, U,之后返回R, 默认实现为minBy和maxBy就是取stream的最值,最要代码就是(a,b) -> comparator.compare(a, b) >= 0 ? a : b; 返回一个值
//比如实现求和操作, 这里return的就是求和之后的m1 reduce(m1, m2 -> m1 += m2)
-
reduce(T identity, BinaryOprator accumulator) 和上面相比,可以接受一个初始值, 返回的对象的初始值, 这里就是m1为100
//比如实现在100的基础上,再加上stream中的所有sum //这里100的类型和 return的m1类型相同 reduce(100, (m1, m2) -> m1 += m2)
-
reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOpretor<U》, BinaryOperator<U》 combiner)
第一个identity代表的返回对象的初始值, accumulator是reduce的计算逻辑, 增加的是一个参数组合器combiner
Stream支持并发操作,为了线程安全,对于reduce操作,每一个线程都会得到不同的result,这个参数的类型必须为返回的数据的类型
identity: 组合函数的处置, 累加器(操作器)的返回结果的初始值
累加器(操作器): 一个关联的、无状态函数, 可以将额外的结果合并到结果中
组合器: 组合两个值的关联、必须和累加器兼容, 只有并行流中才会执行, 也就是普通的stream()不会执行, 只有parallelStream()才会执行
collect 返回集合 collect策略有很多 —> ⭐ 可执行sum、average…等
最常见的collect就是Collectors.toList()策略, 就是将流转为一个List
类型归纳 — 转为集合容器 toXXX
Collectors.toList();
Collectors.toMap();
Collectors.toSet();
Collectors.toCollection();//含参构造,转化为new的容器
Collectors.toConcurrentMap();
分别将流转为List、Map、Set、Collection、 ConcurrentMap
joining 将元素用某规则连接起来
连接符号可以是delimiter连接符; 或者delimiter连接符、prefix开始符,suffix结束符; 除此之外,还可以空参,代表delimiter为空格
Collectors.joining(); // 以空格连接
Collectors.joining("-"); //以-连接
Collectors.joining("【",",","】"); //以【x, y, z.....】
collectingAndThen 先对结果进行归纳,之后再进行Function函数处理结果
结果操作就是将collect结果再执行
List<String> list = Arrays.asList("cfeng", "java", "数字大屏");
String str = list.stream().collect(Collectors.collectingAndThen(Collectors.joining(",","[","]"), s -> s + "你好")); System.out.println(str);
groupingBy 按照条件对结果分组
和SQL的group by类似,内存分组可以减少压力
比如
// 按照字符串长度进行分组 符合条件的元素将组成一个 List 映射到以条件长度为key 的 Map<Integer, List<String>> 中
servers.stream.collect(Collectors.groupingBy(String::length))
为了保证线程安全,可以采用安全的Map
Supplier<Map<Integer, Set<String>>> mapSupplier = () -> Collections.synchronizedMap(new HashMap<>());
Map<Integer, Set<String>> collect = servers.stream.collect(Collectors.groupingBy(String::length, mapSupplier, Collectors.toSet()));
或者可以直接使用groupingByConcurrent
counting 对收集的结果求和
list.stream().count() 效果一样
list.sream().collect(Collectors.counting())
mapping(mapper, downSream) 在Collect中进行隐射
List<String> names = students.stream().collect(Collectors.mapping(Student::getName, Collectors.toList()));
maxBy(comparator)/minBy
大小元素的操作,和list.stream.max终端操作一样
list.stream().collect(Collectors.minBy(Comparator.comparingInt(String.length))) //字符串中比较按照长度比较
list.stream().min(Comparator.comparingInt(String.length))
summingInt/Double/Long(元素 – 可用Lambda)
累加操作,和sum()终端操作一样
list.stream().collect(Collectors.summingInt(s -> s.length))) //字符串中比较按照长度比较
list.stream().min(Comparator.comparingInt(String.length))
summarizingDouble/Int/Long⭐ 统计量: 包括总数 和 max/min 平均值
summarizing对应的就是统计量,会将总数、总和、max、min、avg等统计量提取出来放到IntSummaryStatisics
(Double、 Long) 等统计对象中
IntSummaryStatistics statistics = list.stream().collect(Collectors.summarizingInt(s -> s.length)));
需要的数据再从对象中直接取出即可
reducing ⭐ (T, T) -> T 元素两两比较淘汰一个(一轮轮reduce)
这个方法对用的终端操作也就是reduce
其参数为BinaryOperator< T》 , 给两个相同类型的量,返回一个同类型的结果, (T, T) -> T, 默认实现为maxBy和minBy, 也就是返回最大/小值, 元素两两比较根据给定的策略淘汰一个, 随着迭代的进行,元素reduce
下面是一个例子
//统计城市个子最高的人
Comparator<Person> byHeight = Comparator.comparing(Person::getHeight); //子当以比较器为比较Person的身高
Map<String, Optional<Person>> tallestByCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(BinaryOperator.maxBy(byHeight))));
workProj中应用
首先cfeng想表明的stream确实便捷,同时使用stream不是单纯为了炫技,而是可以简练代码,增强可读性, 数据量比较小的情况下,iterator的效率高于stream; 但是在大数据量的情况下,还是stream的性能好
通过上面的操作可以看出,Stream的操作和SQL很相似,所以Stream主要就是在内存中处理数据【像SQL一样处理】
在Cfeng的work过程中,有客户数据库表的概念,也就是数据库表作为用户的数据,由用户进行相关的SQL操作,由于客户数据库表不是server端直接控制, 所以与其相关的数据处理就和传统的数据库表SQL操作有所不同。
//由server控制的数据库表直接使用SQL进行操作即可
//用户控制的数据库表 用户的界面是可视化的,并且用户不懂SQL,所以传入的只是条件,就像相关数据库工具一样
因此程序中可以dynamic的根据用户条件StringBuilder一个SQL出来
之后利用jdbcTemplate的queryList等方法执行SQL即可
但是因为直接使用的是jdbcTemplate查询出来的数据,所以一个视图对应的是一个List<Map<String,Object>> ---- 一个map对应的是一个元组
那么这里处理这种List<Map<String,Object>>, 比如进行Group、Order相关SQL操作,使用Stream就很好处理了
现在比如我给一个模糊化的需求: 对于一个数据库表【商城名称、商城账户、成交时间、回执单数、交易总数、更新日期】,其中可能有数据【商城名称、商城账户、成交时间】相同,但是更新日期不同,认为这种数据为重复数据,需要取更新日期最新的数据,处理之后再按照商城名称进行分组求回执单数和交易总数的和
可以看到这里涉及三个操作: 去重 -> Group -> Sum, 去重可以认为是算法,Group、sum就是内存中进行SQL操作,Stream发挥重要作用
不使用Stream(假设数据按照key有序)
其实这就是一个算法题, 首先给一个List<String》的primaryFields代表元组标识字段(主码),deduplicatedField代表去重字段(以该字段为key进行去重)
这里给一下出现的数据的封装对象
public class ParamObj {
List<String> primaryFields; //主码
String deduplicatedField; //去重key
String groupField; //group字段
List<String> sumField; //sum字段
SqlParam assetParams; //拼接SQL的相关字段
class SqlParam {
public static final Order CREATE_TIME_ORDER = new Order("create_time", Sort.Direction.DESC); //按照create_time降序Order
//客户数据库表名称
private String name;
//分页页号
private Integer pageNo;
//分页页面大小
private Integer pageSize;
//分组字段
private List<String> groupByFields;
//聚合对象(聚合字段以及聚合方式)
private List<GatherField> quotas;
//过滤对象(过滤字段和方式 <= >= ...)
private List<FieldFilter> filters;
//排序对象(排序字段和方式)
private List<Order> orders;
}
}
class GatherField {
String field; //字段名称
String alias; //字段别名, 在where之前的自定义视图字段名
GatherType type; //GatherType(enum); NO,AVG,SUM,MAX, MIN,COUNT
//和field组合可以成为sum(field)
}
简单容易思考的解法: 设置一个map的过滤器,让数据依次通过这个过滤器,去重之后,再进行group和相关SQL操作(全部都不使用Stream)
public List<Map<String, Object>> deduplicateMapData(ParamObj paramObj) {
/**
* 拼接相关字段进行Sql的build
**/
//... 这里都是获取data所做的处理,比如分组group字段加上update_time,Order使用time
/**
* 这里就是抽象的利用jdbcTemplate获取客户数据库表的数据(有重复)
**/
List<Map<String, Object>> resp = getMapData(paramObj);
Map<String, Object> mapFilter = new HashMap<>();
Map<String, Object> firstRecord = resp.get(0);
//初始化filter
for(String s : originDistinctFields) {
mapFilter.put(s, firstRecord.get(s));
}
//遍历所需参数
Map<String, Object> recordMap;
Set<Map.Entry<String, Object>> filter;
boolean needDelete;
for(int i = 1; i < resp.size(); i ++) {
filter = mapFilter.entrySet();
needDelete = true;
recordMap = resp.get(i);
//对于每一行记录,只需要取第一个,因为group by会默认order, update_time为最大的时间
for(Map.Entry<String, Object> entry : filter) {
if(!recordMap.get(entry.getKey()).equals(entry.getValue())) {
//不相等时,不需要删除改行记录,更新filter
needDelete = false;
for (String s : originDistinctFields) {
mapFilter.put(s, recordMap.get(s));
}
break;
}
}
if(needDelete) {
//重复数据都需要删除
resp.remove(i);
i -= 1;
}
}
if(originGroupByFields.size() == originDistinctFields.size()) {
return resp;
}
//当groupFields.size < distinctFields.size, 需要手动聚合
List<Map<String, Object>> res = new ArrayList<>();
Map<String, Object> tempMap = new HashMap<>();
firstRecord = resp.get(0);
for(GatherField gatherField : originQuotas) {
tempMap.put(gatherField.getField(),firstRecord.get(gatherField.getField()));
}
res.add(tempMap);
boolean needInsert;
for(int i = 1; i < resp.size(); i ++) {
//如果res集合中groupByField相同,则需要聚合; 否则插入即可
recordMap = resp.get(i);
needInsert = true;
boolean isDuplicate; //是否重复
//对于每一行记录,检查res中是否有相同的
for(int j = 0; j < res.size(); j ++) {
isDuplicate = true;
tempMap = res.get(j);
for(String s : originGroupByFields) {
if(!tempMap.get(s).equals(recordMap.get(s))) {
//不相等, 继续比较res的下一个元素
isDuplicate = false;
break;
}
}
if(isDuplicate) {
//如果当前元素重复, 那么就覆盖,不需要插入
needInsert = false;
//TODO:recordMap 覆盖tempMap
for(GatherField gatherField : quotasFields) {
switch (gatherField.getType()) {
case SUM:
case COUNT:
tempMap.put(gatherField.getField(), (double)tempMap.get(gatherField.getField()) + (double)recordMap.get(gatherField.getField()));
continue;
case AVG:
tempMap.put(gatherField.getField(), ((double)tempMap.get(gatherField.getField()) + (double)recordMap.get(gatherField.getField()))/2.0);
continue;
case MAX:
tempMap.put(gatherField.getField(), Math.max((double)tempMap.get(gatherField.getField()), (double)recordMap.get(gatherField.getField())));
continue;
case MIN:
tempMap.put(gatherField.getField(), Math.min((double)tempMap.get(gatherField.getField()), (double)recordMap.get(gatherField.getField())));
}
}
break;
}
}
//遍历完成之后,如果需要插入
if(needInsert) {
//插入res,是完成后才插入,不需要变换j
Map<String, Object> node = new HashMap<>();
for(GatherField gatherField : originQuotas) {
node.put(gatherField.getField(),recordMap.get(gatherField.getField()));
}
res.add(node);
}
}
return res;
}
可以看到过程非常麻烦,后面的聚合Gather也是使用的手动聚合,flag标志过多,可读性和可维护性差
使用Stream优化
首先就是去重过程,可以看到使用一个Map作为filter的方式可读性很差,我们可以将primaryFields字段拼接为String, 以string为Key, 对应的Map为value进行去重,因为数据按照定义的deduplication字段有序,所以只需要取第一个出现的作为value
这里测试的时候,首先需要mock数据
private List<Map<String, Object>> generateData() {
List<Map<String, Object>> resp = new ArrayList<>();
Map<String, Object> record = new HashMap<>();
record.put("商城名称", "招商大魔方");
record.put("商城账户", "23513153451145");
record.put("成交时间", "2023/02/23");
record.put("回执单数", 10);
record.put("交易总数", 9);
record.put("更新日期", "2023-02-23 14:59:20");
resp.add(record);
Map<String, Object> record1 = new HashMap<>();
record1.put("商城名称", "宏帆广场");
record1.put("商城账户", "1235353451145");
record1.put("成交时间", "2023/02/23");
record1.put("回执单数", 7);
record1.put("交易总数", 9);
record1.put("更新日期", "2023-02-23 14:59:20");
resp.add(record1);
Map<String, Object> record2 = new HashMap<>();
record2.put("商城名称", "招商大魔方");
record2.put("商城账户", "23513153451145");
record2.put("成交时间", "2023/02/24");
record2.put("回执单数", 6);
record2.put("交易总数", 6);
record2.put("更新日期", "2023-02-24 14:59:20");
resp.add(record2);
Map<String, Object> record3 = new HashMap<>();
record3.put("商城名称", "农业广场");
record3.put("商城账户", "23513153458123");
record3.put("成交时间", "2023/02/25");
record3.put("回执单数", 10);
record3.put("交易总数", 8);
record3.put("更新日期", "2023-02-25 13:59:20");
resp.add(record3);
Map<String, Object> record4 = new HashMap<>();
record4.put("商城名称", "宏帆广场");
record4.put("商城账户", "1235353451145");
record4.put("成交时间", "2023/02/23");
record4.put("回执单数", 5);
record4.put("交易总数", 3);
record4.put("更新日期", "2023-02-23 14:59:20");
resp.add(record4);
Map<String, Object> record5 = new HashMap<>();
record5.put("商城名称", "招商大魔方");
record5.put("商城账户", "23513153451145");
record5.put("成交时间", "2023/02/24");
record5.put("回执单数", 6);
record5.put("交易总数", 6);
record5.put("更新日期", "2023-02-24 14:59:20");
resp.add(record5);
Map<String, Object> record6 = new HashMap<>();
record6.put("商城名称", "农业广场");
record6.put("商城账户", "23513153458123");
record6.put("成交时间", "2023/02/25");
record6.put("回执单数", 10);
record6.put("交易总数", 8);
record6.put("更新日期", "2023-02-25 13:59:20");
resp.add(record6);
Map<String, Object> record7 = new HashMap<>();
record7.put("商城名称", "万达广场");
record7.put("商城账户", "2351387834758123");
record7.put("成交时间", "2023/02/23");
record7.put("回执单数", 5);
record7.put("交易总数", 3);
record7.put("更新日期", "2023-02-23 14:59:20");
resp.add(record7);
Map<String, Object> record8 = new HashMap<>();
record8.put("商城名称", "艾尔摩尔");
record8.put("商城账户", "123243543634112");
record8.put("成交时间", "2023/02/24");
record8.put("回执单数", 6);
record8.put("交易总数", 6);
record8.put("更新日期", "2023-02-24 14:59:20");
resp.add(record8);
Map<String, Object> record9 = new HashMap<>();
record9.put("商城名称", "华夏摩登");
record9.put("商城账户", "123445568123679");
record9.put("成交时间", "2023/02/27");
record9.put("回执单数", 14);
record9.put("交易总数", 14);
record9.put("更新日期", "2023-02-27 13:59:20");
resp.add(record9);
return resp;
}
之后就是对数据进行去重
List<String> primaryFields = Arrays.asList("商城名称", "商城账户", "成交时间");
String groupField = "成交时间"; //按照商城名称分组
List<String> sumFields = Arrays.asList("回执单数", "交易总数");
//mock数据
List<Map<String, Object>> mockData = generateData();
//去重, 将primaryKey拼接在一起为key, Map当作value, 这样只需要填充map即可,最后取所有的value进行后操作
HashMap<String, Map<String, Object>> deduplicatedMap = new HashMap<>();
System.out.println("去重前的总数:" + mockData.size());
for(Map<String, Object> betaData: mockData) {
StringBuilder primaryValue = new StringBuilder();
for(Map.Entry<String, Object> entry : betaData.entrySet()) {
//拼接primary的值
if(primaryFields.contains(entry.getKey())) {
primaryValue.append(entry.getValue());
}
}
//第一个可以put入,后面的就不能放入,可以使用map的putIfAbsent方法
deduplicatedMap.putIfAbsent(primaryValue.toString(),betaData);
}
System.out.println("去重后的总数: " + deduplicatedMap.size());
System.out.println(deduplicatedMap.values());
去重之后对结果进行分组求和,group by, sum()
//需要对结果进行分组之后进行求和等相关操作,使用Stream
//groupBy后,数据就是groupFiled : List<原对象>, 按照groupField进行分组划分为多个list
//对于多个list, 最后需要合并为一个stream,所以使用flatMap【合并stream,所以flatMap中应该为stream】
//对于每组list,需要求给定的sumFields的和, 使用reduce【返回Stream一个值,max/min/sum/avg为特例】,返回的是Optional,需要get
List<Map<String, Object>> res = deduplicatedMap.values().stream().collect(Collectors.groupingBy(map -> map.get(groupField)))
.values().stream().flatMap(list -> Stream.of(list.stream().reduce((m1, m2) -> {
//T, T -> T
//对分组内的sunFields求和, m1和m2就是抽象的list中的两个map【一次迭代】
for(String sumField : sumFields) {
double value1 = Double.parseDouble(m1.get(sumField).toString());
double value2 = Double.parseDouble(m2.get(sumField).toString());
m1.put(sumField, value1 + value2);
}
return m1;
}).get())).collect(Collectors.toList());
System.out.println(res);
最后得到的结果如下
[
{更新日期=2023-02-23 14:59:20, 成交时间=2023/02/23, 商城账户=1235353451145, 商城名称=宏帆广场, 回执单数=7, 交易总数=9},
{更新日期=2023-02-25 13:59:20, 成交时间=2023/02/25, 商城账户=23513153458123, 商城名称=农业广场, 回执单数=10, 交易总数=8},
{更新日期=2023-02-24 14:59:20, 成交时间=2023/02/24, 商城账户=23513153451145, 商城名称=招商大魔方, 回执单数=6, 交易总数=6},
{更新日期=2023-02-23 14:59:20, 成交时间=2023/02/23, 商城账户=23513153451145, 商城名称=招商大魔方, 回执单数=10, 交易总数=9},
{更新日期=2023-02-23 14:59:20, 成交时间=2023/02/23, 商城账户=2351387834758123, 商城名称=万达广场, 回执单数=5, 交易总数=3},
{更新日期=2023-02-27 13:59:20, 成交时间=2023/02/27, 商城账户=123445568123679, 商城名称=华夏摩登, 回执单数=14, 交易总数=14},
{更新日期=2023-02-24 14:59:20, 成交时间=2023/02/24, 商城账户=123243543634112, 商城名称=艾尔摩尔, 回执单数=6, 交易总数=6}
]
# 按照商城账户分组求和
[
{更新日期=2023-02-25 13:59:20, 成交时间=2023/02/25, 商城账户=23513153458123, 商城名称=农业广场, 回执单数=10, 交易总数=8},
{更新日期=2023-02-24 14:59:20, 成交时间=2023/02/24, 商城账户=23513153451145, 商城名称=招商大魔方, 回执单数=16.0, 交易总数=15.0},
{更新日期=2023-02-24 14:59:20, 成交时间=2023/02/24, 商城账户=123243543634112, 商城名称=艾尔摩尔, 回执单数=6, 交易总数=6},
{更新日期=2023-02-23 14:59:20, 成交时间=2023/02/23, 商城账户=1235353451145, 商城名称=宏帆广场, 回执单数=7, 交易总数=9},
{更新日期=2023-02-27 13:59:20, 成交时间=2023/02/27, 商城账户=123445568123679, 商城名称=华夏摩登, 回执单数=14, 交易总数=14},
{更新日期=2023-02-23 14:59:20, 成交时间=2023/02/23, 商城账户=2351387834758123, 商城名称=万达广场, 回执单数=5, 交易总数=3}
]
# 按照成交时间分组求和
[
{更新日期=2023-02-25 13:59:20, 成交时间=2023/02/25, 商城账户=23513153458123, 商城名称=农业广场, 回执单数=10, 交易总数=8},
{更新日期=2023-02-27 13:59:20, 成交时间=2023/02/27, 商城账户=123445568123679, 商城名称=华夏摩登, 回执单数=14, 交易总数=14},
{更新日期=2023-02-24 14:59:20, 成交时间=2023/02/24, 商城账户=23513153451145, 商城名称=招商大魔方, 回执单数=12.0, 交易总数=12.0},
{更新日期=2023-02-23 14:59:20, 成交时间=2023/02/23, 商城账户=1235353451145, 商城名称=宏帆广场, 回执单数=22.0, 交易总数=21.0}
]
可以看到使用Stream因为可以调用现成的api,所以减少了很多工作量, 只是这个逻辑中需要注意几个地方:
- flatMap相较于map作用是合并Stream, 所以使用reduce最终获得一个结果后,需要封装为Stream对象,使用Stream.of()
- reduce其实就是T,T -> T(最简单的应用),就是stream中两个对象的迭代模式,max/min/sum/avg都可以用reduce替代, reduce的功能更强大, reduce需要返回一个对象,最后返回的就是两两迭代后最红一个对象
- 对于这种List<map《String, Objec>>的处理,需要擅用map的相关values()和putIfAbsent()等方法,同时使用Stream整体的可阅读性更好