前言:我们假设有这样一个场景,一个电商订单中,有各种优惠券促销活动,比如一个订单有用了店铺优惠券(减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;
    }
}

本文仅记录下学习过程中的一些收获,如有不足之处,请指正或者提出好的建议。◕‿◕。谢谢。


Fiuty
16 声望1 粉丝

一砖一瓦