前言:我们假设有这样一个场景,一个电商订单中,有各种优惠券促销活动,比如一个订单有用了店铺优惠券(减100),商品优惠券(减50),满减优惠券(减35),当然优惠券可以叠加,比如这个单有用了两张店铺优惠券,我们需要统计某类优惠券共减去多少金额,处理成一个Map集合,Map集合的键key是优惠券名称,值value是金额。本文涉及的代码在github上,点击 链接 可查看源码。
- 优惠券Coupon类(此处省略get、set、constructor等方法):
public class Coupon {
/**
* 优惠券名称
*/
private String name;
/**
* 优惠券金额
*/
private BigDecimal amount;
}
我们假设订单中的优惠券信息如下:
{
"couponList":[
{
"店铺优惠券":100
},
{
"商品优惠券":50
},
{
"店铺优惠券":100
},
{
"满减优惠券":35
}
]
}
这里简单地写一个方法来表示订单中的优惠券金额集(请忽略魔法值),方法返回一个Coupon优惠券集合:
private static List<Coupon> couponList() {
Coupon coupon1 = new Coupon("店铺优惠券", new BigDecimal("100"));
Coupon coupon2 = new Coupon("商品优惠券", new BigDecimal("50"));
Coupon coupon3 = new Coupon("店铺优惠券", new BigDecimal("100"));
Coupon coupon4 = new Coupon("满减优惠券", new BigDecimal("35"));
List<Coupon> couponList = new ArrayList<>(4);
couponList.add(coupon1);
couponList.add(coupon2);
couponList.add(coupon3);
couponList.add(coupon4);
return couponList;
}
我们所需要最终的结果是这样子的:
{店铺优惠券=200,商品优惠券=50, 满减优惠券=35 }
- 方式一,方法入参是一个Coupon优惠券集合,这里用了一个if else判断语句,先判断是否已经有存放键值对了,如果有的话,值作累加,如果没有的话,直接放键值对到map集合里,代码如下:
private static Map<String, BigDecimal> cumulative1(List<Coupon> coupons) {
//key name,value cumulative price
Map<String, BigDecimal> couponMap = new HashMap<>(5);
coupons.forEach(coupon -> {
String nameKey = coupon.getName();
BigDecimal amountValue = coupon.getAmount();
if (couponMap.containsKey(nameKey)) {
couponMap.put(nameKey, couponMap.get(nameKey).add(amountValue));
} else {
couponMap.put(nameKey, amountValue);
}
});
return couponMap;
}
- 方式二,这里比方式一简洁一点,在设置value的时候用了三元运算符,先判断是否有键值对了,有的话同样累加,没的话设置值:
private static Map<String, BigDecimal> cumulative2(List<Coupon> coupons) {
//key name,value cumulative price
Map<String, BigDecimal> couponMap = new HashMap<>(5);
coupons.forEach(coupon -> {
String nameKey = coupon.getName();
BigDecimal amountValue = coupon.getAmount();
couponMap.put(nameKey, couponMap.get(nameKey) == null ? amountValue : couponMap.get(nameKey).add(amountValue));
});
return couponMap;
}
- 方式三,lambda优化对map集合的处理:
private static Map<String, BigDecimal> cumulativeByLambda(List<Coupon> coupons) {
//key name,value cumulative price
Map<String, BigDecimal> couponMap = new HashMap<>(5);
coupons.forEach(coupon -> {
String nameKey = coupon.getName();
BigDecimal amountValue = coupon.getAmount();
couponMap.merge(nameKey, amountValue, (oldValue, newValue) -> oldValue.add(newValue));
});
return couponMap;
}
这里Map集合的merge方法是Java8新增的一个默认方法,方法有三个参数,第一个参数是key,第二个参数是value,而第三个参数针对函数式接口的一个实现,merge方法帮我们写了如果map集合无该key的话,就存放value,如果map集合已经存在该key了,那么具体要怎么存放新的value,由你来实现,这里留了一个BiFunction<T,U,R>函数式接口,而我们的需求是累加优惠券金额,所以这里我写了oldValue.add(newValue),实际上对应第三个参数我们还可以写得更简洁些,可以用方法引用BigDecimal::add,即如下:
private static Map<String, BigDecimal> cumulativeByLambda(List<Coupon> coupons) {
//key name,value cumulative price
Map<String, BigDecimal> couponMap = new HashMap<>(5);
coupons.forEach(coupon -> {
String nameKey = coupon.getName();
BigDecimal amountValue = coupon.getAmount();
couponMap.merge(nameKey, amountValue, BigDecimal::add);
});
return couponMap;
}
我们点进去merge方法里面,看看源码:
public interface Map<K,V> {
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
}
我们可以看到,这里关键之处在于merge方法的第三个参数,给我们留了一个函数式接口BiFunction<T,U,R>,再点进去这个BiFunction<T,U,R>函数式接口:
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
apply方法待实现,方法参数是泛型类T和U,返回参数是泛型类R,简而言之就是我们需要实现一个方法,方法传入T和U,方法R。
- 好了,继续看回merge方法的实现,题外话,default是修饰merge方法,声明该方法是一个默认方法,default是Java8新出的一个关键字,用于对旧接口新增一个方法,而实现该接口的实现类无需实现该新增方法,可以在接口对该方法进行实现。
- 从merge源码我们可以看到,方法最初先用requireNonNull来对函数式接口的实现以及map集合的value作判空。
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
- 接着我们看到用key去取出oldValue,声明一个新的Value(newValue),如果oldValue为空的话,说明该map集合之前无存放键值对,那么newValue被赋值为merge方法传进来的value,如果oldValue不为null(说明之前已经存放了键值对),那么这里执行函数式接口remappingFunction.apply(oldValue, value),第一个参数是oldValue,即之前已经存放的键值对的值value,第二个参数是即将存放进来的value,而对该函数式接口的实现由我们调用merge方法时第三个参数进行实现,我们做的是累加金额,故传了一个旧value累加新value的方法。
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
- 最后是排除掉newValue也为空的情况,正常情况是处理完累加就put进来map集合里。
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
- merge源码:
public interface Map<K,V> {
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
}
本文仅记录下学习过程中的一些收获,如有不足之处,请指正或者提出好的建议。◕‿◕。谢谢。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。