背景:
1.金融业中,通常存在对账行为。
2.对账的本质是比较双方交易记录。
3.双方都有的交易记录,比对【状态 & 金额 】,我们只捡重点说哈。
3.1.状态金额均相同,记为成功,反之则失败。
4.一方有另一方无的,我们记为长短款。
Java8中新特性,在本博中,主要应用 Function && Stream。
对账,通常有上游数据[reList],与本地数据[loList]。我假设其数据结构为List<Map>。
先定义一个BiFunction,两个入参,一个返回值。remote为上游数据,local为本地数据。
代码如下:
static BiFunction<List<Map>, List<Map>, Map> diff = (remote, local) -> { null };
通常我们嵌套循环,遍历两个集合。这种操作太传统了。如果两个集合长度均为百万级,那还是有点莽。
我们来取个巧,我们将其中一个集合转为字典。
为什么要将List转成Map呢,机智的小伙伴们已经明白了。因为可以把嵌套循环拆分,减少数量级。
我们只需要循环一个List,从其中取出订单号,再根据订单号[map的key]从字典[Map]中取出本地订单[map的value]。
我们已经假设了数据结构,所以,我们把交易订单号当作Map的key,交易订单当作value。
我们可以利用Java8中的新特性。代码如下:
Map<String,Map> localMap =
local.stream().collect(Collectors.toMap(map -> map.get("orderNo").toString(), Function.identity()));
Collectors.toMap(keyFunc, valFunc) 方法接收的参数为Lambda表达式,作用很明显就是把集合转成字典【Map】。
至于字典的key,value要从哪来呢,从集合中的对象来嘛。怎么来呢?
你自己写个 Function 去实现吧,反正我让你传两个 Function 分别用来实现key 跟 value。
所以 keyFunc 用来生成我们转换后的Map的key的,valFunc大家都知道了,就是用来生成我们Map的value的。
map.get("orderNo").toString(),我能理解,是从订单记录中取订单号,然后当作Map的key嘛。
那 Function.identity() 又是个啥呢?别急哈,这个就是说,你传入的参数是啥,我就返回啥。
我们是把订单当作Map的值的嘛,所以,你当然也可以把 Function.identity() 写成 map -> map 呀!
到这里,我们已经把一个List 转成一个Map了。
可爱的杠精小伙伴就会说,你说这么多,好复杂。我直接用循环遍历List,再转成Map一样能实现,还简单。
嗯嗯,其实我也会,代码如下:
public Map toMap(List<Map> list){
Map reslut = new HashMap();
for (Map map: list){
reslut.put(map.get("orderNo"),map);
}
return reslut;
}
可是,本博是为了跟大家唠嗑唠嗑新特性嘛。所以杠精,不要再反驳我㕸...嘿嘿!
好了,题归正传。我们继续。
我们要遍历上游数据,然后把上游数据跟本地数据进行比较。来循环走起来。
public void diff(List<Map> list){
/**
* 上一步将本地订单,由List转成了Map
*/
Map<Object,Map> loMaps = new HashMap();
for (Map reMap : list){
//reMap 为上游订单
Object orderNo = reMap.get("orederNo");
//loMap 本地订单
Map loMap = loMaps.get(orderNo);
/**
* 吧啦吧啦,拿着上游订单[reMap]跟本地订单[loMap]去对比吧。
* 这个具体对比过程,就是对账,对比金额,状态,时间什么的。
* 然后是对账结果入库或落地。
*/
}
}
对账就完成了,哈哈哈,完美。 ... 等等,好像有哪里不对。我们不是要用新特性的吗?
稍等,让我冥想1秒钟,好了,代码出来了:
public void diff(List<Map> list){
/**
* 上一步将本地订单,由List转成了Map
*/
Map<Object,Map> loMaps = new HashMap();
list.stream().forEach(reMap -> {
//reMap 为上游订单
Object orderNo = reMap.get("orederNo");
//loMap 本地订单
Map loMap = loMaps.get(orderNo);
/**
* 吧啦吧啦,拿着上游订单[reMap]跟本地订单[loMap]去对比吧。
* 这个具体对比过程,就是对账,对比金额,状态,时间什么的。
* 然后是对账结果入库或落地。
*/
});
}
机智的小伙伴已经发现了,这TM有什么区别吗?不是就是for换成了.stream().forEach吗?
额,大胸弟,莫急。听我慢慢港。list.stream() 就是新特性,将集合流化。
流氓化后就可以有很多骚操作了呀,至于具体有哪些骚姿势,那就需要你自己去慢慢开发了。
... ... 额,不是流氓化,是流化 ... ...
其实我还是个有点节操的人的。既然都已经说到了 BiFunction ,怎么可能不再唠叨两句呢?
上面也太散了,好像有点懵。那就最后再整理一下吧!
/**
* 对账
* <ol>
* <li>remote 为上游订单集合</li>
* <li>local 为本地订单集合</li>
* <li>我们假设集合中的对象为Map,当然,也可以是你的自定义JavaBean,譬如OrderInfo什么的</li>
* </ol>
*/
static BiFunction<List<Map>, List<Map>, Map> diff = (remote, local) -> {
/**
* 将本地订单,由List转成Map
* <br>以订单号为key,以订单信息为value
*/
Map<String,Map> loMaps =
local.stream().collect(Collectors.toMap((map) -> map.get("orderNo").toString(), map -> map));
/**
* 将上游数据流化
*/
Stream<Map> stream = remote.parallelStream();
/**
* 对账结果
*/
Map result = new ConcurrentHashMap();
stream.collect(Collectors.toList()).forEach(reMap->{
//reMap 为上游订单
Object orderNo = reMap.get("orederNo");
//loMap 本地订单
Map loMap = loMaps.get(orderNo);
/**
* 吧啦吧啦,拿着上游订单[reMap]跟本地订单[loMap]去对比吧。
* 这个具体对比过程,就是对账,对比金额,状态,时间,长短款什么的。
* 然后是对账结果入库或落地。
* 也可以将对账结果存入result
* 如果有业务需要,将对账结果响应出去,由调用者另行处理。
*/
});
/**
* 返回对账结果
*/
return result;
};
小伙伴们,是不是觉得,你写这个 BiFunction 还不如我写个方法呢。确实有点道理哈!
我们是要用新特性嘛,所以老铁,别太在意这些细节。
那么,方法大家都知道怎么调用,这个 BiFunction 要怎么调用呢?
BiFunction 申明成变量,直接调用BiFunction对象的apply(obj1, obj2)。
BiFunction 与 Function 的区别是,前者有两个入参,后者只有一个入参。
也许有小伙伴说了,只有一两个入参,我要是需要五六个入参,不就GG了。
Java是面向对象编程的语言,你随便整个对象,要啥属性没有?是吧!
请看代码:
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Demo {
/**
* 对账
* <ol>
* <li>remote 为上游订单集合</li>
* <li>local 为上游订单集合</li>
* <li>我们假设集合中的对象为Map,当然,也可以是你的自定义JavaBean,譬如OrderInfo什么的</li>
* </ol>
*/
static BiFunction<List<Map>, List<Map>, Map> diff = (remote, local) -> {
/**
* 将本地订单,由List转成Map
* <br>以订单号为key,以订单信息为value
*/
Map<String,Map> loMaps =
local.stream().collect(Collectors.toMap((map) -> map.get("orderNo").toString(), map -> map));
/**
* 将上游数据流化
*/
Stream<Map> stream = remote.parallelStream();
/**
* 对账结果
*/
Map result = new ConcurrentHashMap();
/**
* 遍历上游订单,跑对账业务
*/
stream.collect(Collectors.toList()).forEach(reMap->{
//reMap 为上游订单
Object orderNo = reMap.get("orederNo");
//loMap 本地订单
Map loMap = loMaps.get(orderNo);
/**
* 吧啦吧啦,拿着上游订单[reMap]跟本地订单[loMap]去对比吧。
* 这个具体对比过程,就是对账,对比金额,状态,时间,长短款什么的。
* 然后是对账结果入库或落地。
* 也可以将对账结果存入result
* 如果有业务需要,将对账结果响应出去,由调用者另行处理。
*/
});
/**
* 返回对账结果
*/
return result;
};
public static void main(String[] args) {
//上游订单
List<Map> re = new ArrayList();
//本地订单
List<Map> lo = new ArrayList();
/**
* 不得了了,我们就假装他们有几百万条数据吧
*/
System.out.println("开始对账");
Map map = diff.apply(re, lo);
System.out.println("对账完成");
System.out.println("对账结果" + map);
}
}
好啦!代码都贴完了 ... 我应该也没啥好说的了。
不过有个细心的小伙伴又问我了,为什么你上面遍历上游数据时一开始用 stream.forEach( ... 后来又用 stream.collect(Collectors.toList()).forEach( ... 。很好,这种细节都被你们发现了。
其实吧,关键还是在于流化方式。
{ Stream<Map> stream = remote.parallelStream(); } 这是一个并行流
{ Stream<Map> stream = remote.stream(); } 这是一个串行流
stream.collect(Collectors.toList()).forEach 是为了在并行流下,保证线程安全。