由于公司的api做了升级,所以最近要迁移自己写的druid查询部分的代码到新项目,然后在迁移的时候有一段用到lambda表达式来求和的例子,时间有些长了一开始看还有些懵,又重新温习了一下,下面写一下整体思路吧(以下是自己编的例子,和业务思想一样):
需求:需要通过druid api根据matchType作为透视维度,查看每个matchType的点击量和薪水,构造的对象如下:
@Data // lombok注解
@Builder // lombok注解
static class Person {
private double salary;
private int click;
private String matchType;
}
执行的伪sql为:SELECT SUM(salary) AS salary, SUM(click) AS click, matchType FROM DRUID_XXXX GROUP BY matchType
通过druid api查询得到处理之后的结果如下:
{
"result": [
{
"salary": 100.1,
"click": 101,
"matchType": "pc"
},
{
"salary": 100.2,
"click": 102,
"matchType": "mb"
},
{
"salary": 100.3,
"click": 103,
"matchType": "ny"
}
]
}
把以上的结果构造成JsonObject如下:
List<JsonObject> result = Arrays.asList(
new Gson().toJsonTree(Person.builder().salary(100.1).click(101).matchType("pc").build()).getAsJsonObject(),
new Gson().toJsonTree(Person.builder().salary(100.2).click(102).matchType("mb").build()).getAsJsonObject(),
new Gson().toJsonTree(Person.builder().salary(100.3).click(103).matchType("ny").build()).getAsJsonObject()
);
由于druid的查询api对sum的指标有要求,这里提一下为下面写函数做准备:double类型的字段求和计算公式是doubleSum;int或者long类型的字段的求和计算公式是longSum(区别mysql,mysql求和无论double或者long/int都是sum),我们再写一个JavaBean保存一下上面指标的计算公式(其实就是指标的类型信息,long或者double),javaBean如下:
@Data // lombok注解
@Builder // lombok注解
static class Agg {
private String name;
private String type;
}
List<Agg> aggregations = Arrays.asList( // 这样查询结果所有指标的类型就存到aggregations这里面了
Agg.builder().name("salary").type("doubleSum").build(),
Agg.builder().name("click").type("longSum").build()
);
下面是最重要的部分:通过reduce求每个指标的合计
JsonObject jsonObject = result.stream().reduce(result.get(0),
(iterate, item) -> {
// 我们需要把第一个JsonObject的维度信息去掉, 最后的合计不需要维度
if (iterate == item) {
requestJson.getDimensions().forEach(item::remove);
return item;
}
aggregations.forEach(aggregation -> {
String name = aggregation.getName();
if ("doubleSum".equalsIgnoreCase(aggregation.getType())) {
iterate.addProperty(name, iterate.get(name).getAsDouble() + item.get(name).getAsDouble());
} else {
iterate.addProperty(name, iterate.get(name).getAsLong() + item.get(name).getAsLong());
}
});
return iterate;
}
);
这里解释一下,我们可以先看一下reduce函数的源码:
T reduce(T identity, BinaryOperator<T> accumulator);
T identity:identity的英语翻译是恒等式,也就是计算(函数)的初始值
BinaryOperator<T> accumulator:accumulator的英语翻译是累加器,也就是我们的函数。
可以看一下源码方法官方给的解释:
This is equivalent
* to:
* <pre>{@code
* T result = identity;
* for (T element : this stream)
* result = accumulator.apply(result, element)
* return result;
* }</pre>
*
这个解释应该很通俗易懂了:reduce方法的第一个参数作为初始值赋给apply方法的第一个参数,然后遍历当前流通过apply(我们自己的业务逻辑)实现求合计或者其他的目的,本章中是求合计
然后再回头看上面自己实现的代码:
我们把reduce方法中的第一个参数设置为result集合的第一个元素,该元素的类型是JsonObject
第二个参数,(iterate, item) 参数列表中的 iterate 第一次代表 reduce的第一个参数,也就是result.get(0),以后每次都代表求和之后的JsonObject。item代表result集合的流,第一次也是从result.get(0)开始,所以第一次开始执行时如果不处理的话会多算一次result.get(0)的JsonObject中的指标,顺便我们可以把result.get(0)的JsonObject中的的维度去掉,最终结果中我们只需要指标的合计,并不需要维度。
(iterate, item) -> {...} 大括号中的实现逻辑看上面“最重要的部分”的代码,这是我们的求和函数: 遍历指标名称,通过指标类型求和后返回累加后的结果,最终返回的结果打印如下:
{"salary":300.6,"click":306}
小小注意事项:
1. reduce函数在第一次执行时如果我们没有处理维度信息,返回的结果打印如下:
{"salary":300.6,"click":306,"matchType":"pc"}
会加上result集合第一个元素中的维度信息。
2. reduce函数在第一次执行时,由于初始值和当前流的第一个元素相同,如果不处理(把if (iterate == item)的逻辑去掉),返回的结果打印如下:
{"salary":400.7,"click":407,"matchType":"pc"}
可以看到多算了result集合第一个元素中的指标一次
** 附上整体的代码:
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import lombok.Builder;
import lombok.Data;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ReduceTest {
public static void main(String[] args) {
List<JsonObject> result = Arrays.asList(
new Gson().toJsonTree(Person.builder().salary(100.1).click(101).matchType("pc").build()).getAsJsonObject(),
new Gson().toJsonTree(Person.builder().salary(100.2).click(102).matchType("mb").build()).getAsJsonObject(),
new Gson().toJsonTree(Person.builder().salary(100.3).click(103).matchType("ny").build()).getAsJsonObject()
);
Dimensions dimensions = Dimensions.builder().dimensions(Collections.singletonList("matchType")).build();
List<Agg> aggregations = Arrays.asList(
Agg.builder().name("salary").type("doubleSum").build(),
Agg.builder().name("click").type("longSum").build()
);
JsonObject jsonObject = result.stream().reduce(result.get(0),
(iterate, item) -> {
// 我们需要把第一个JsonObject的维度信息去掉, 最后的合计不需要维度
if (iterate == item) {
dimensions.getDimensions().forEach(item::remove);
return item;
}
aggregations.forEach(aggregation -> {
String name = aggregation.getName();
if ("doubleSum".equalsIgnoreCase(aggregation.getType())) {
iterate.addProperty(name, iterate.get(name).getAsDouble() + item.get(name).getAsDouble());
} else {
iterate.addProperty(name, iterate.get(name).getAsLong() + item.get(name).getAsLong());
}
});
return iterate;
}
);
System.out.println(jsonObject);
}
@Data
@Builder
static class Person {
private double salary;
private int click;
private String matchType;
}
@Data
@Builder
static class Dimensions {
private List<String> dimensions;
}
@Data
@Builder
static class Agg {
private String name;
private String type;
}
}
===========================割===========================
理解了以后再看没什么,但是要把想法转成文字真的好难。。