由於公司的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;
}
}
===========================割===========================
理解了以後再看沒什麼,但是要把想法轉成文字真的好難。。