Java Stream 不直接计算阶梯价,而是通过将优惠券建模为函数式接口CouponPolicy,结合filter、sorted、reduce等操作筛选、排序并逐个应用可叠加的最优优惠组合,其中阶梯逻辑由各CouponPolicy.apply()方法根据上下文动态实现。

Java Stream 流本身不直接“计算”阶梯价,而是提供一种声明式、函数式的方式组织和组合优惠逻辑。在电商购物车场景中,动态阶梯价叠加筛选的关键在于:把每张优惠券抽象为可复用的计算单元,再用 Stream 按优先级、适用条件、互斥规则等进行筛选与排序,最后逐个应用并累积优惠效果。
把优惠券建模为函数式接口
不要把优惠券硬编码成 if-else,而是定义统一契约:
public interface CouponPolicy {
// 是否能用于当前购物车(含商品、金额、用户等级等上下文)
boolean match(CartContext context);
// 计算本次优惠金额(支持满减、折扣、折上折等)
BigDecimal apply(CartContext context);
// 优先级:数值越小越先应用(如平台券 > 店铺券 > 积分抵扣)
int priority();
// 是否与其他券互斥(如“不可与其他优惠同享”)
boolean exclusive();
}
例如一张满300减50的店铺券:
- match() 判断 cartContext.totalAmount().compareTo(BigDecimal.valueOf(300)) >= 0 && 属于该店铺商品
- apply() 返回 BigDecimal.valueOf(50)
- priority() 返回 20(平台券设为 10,积分抵扣设为 30)
- exclusive() 返回 false(允许叠加)
用 Stream 筛选+排序+去重互斥券
从所有可用券中,按业务规则动态选出一组可叠加的最优组合:
立即学习“Java免费学习笔记(深入)”;
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
- filter(match):先筛出当前购物车能用的券
- sorted(Comparator.comparing(CouponPolicy::priority)):按优先级升序,保证高优券先算
- reduce 逐个应用 + 状态累积:用 CartContext 的副本或 builder 模式记录已减免金额、已标记互斥券,避免重复应用
- 若某券 exclusive() 为 true,后续匹配到的 exclusive 券需 skip(可用 Optional 或自定义 Collector 实现)
示例片段:
List<CouponPolicy> applicable = allCoupons.stream()
.filter(c -> c.match(cartContext))
.sorted(Comparator.comparing(CouponPolicy::priority))
.collect(Collectors.toList());
BigDecimal totalDiscount = BigDecimal.ZERO;
Set<String> usedExclusiveTypes = new HashSet<>();
CartContext current = cartContext;
for (CouponPolicy cp : applicable) {
if (cp.exclusive() && usedExclusiveTypes.contains(cp.getClass().getSimpleName())) {
continue;
}
BigDecimal discount = cp.apply(current);
totalDiscount = totalDiscount.add(discount);
current = current.withDiscountedAmount(discount); // 更新上下文
if (cp.exclusive()) {
usedExclusiveTypes.add(cp.getClass().getSimpleName());
}
}
支持动态阶梯价的核心:上下文感知的 apply()
真正的“阶梯”不是写死在 Stream 里,而是在每个 CouponPolicy.apply() 内部实现:
- 比如“满200减20,满500减80,满1000减200”,apply() 方法会根据 current.totalAmount() 动态判断档位
- 再比如“第二件半价”,apply() 需遍历商品列表,按品类/价格排序后取符合条件的第二件,计算其 50% 折扣
- 叠加时,后应用的券看到的是前一张券处理后的 current(已减掉前面的 discount),天然支持“折上折”逻辑
避免常见陷阱:状态、顺序、精度
Stream 的惰性求值和不可变特性容易掩盖副作用问题:
- 别在 map/filter 里修改外部变量:用 reduce 或 for 循环更可控;Stream.peek() 仅用于调试
- 优先级必须明确且稳定:避免两个券 priority 相同又无次级排序(如再按创建时间),否则结果不确定
- 金额一律用 BigDecimal:避免 double 精度丢失,尤其在多次叠加后误差放大
- 阶梯边界要闭合处理:如满300减50,300.00 可用,299.99 不可用,注意 compareTo 和 scale 一致
不复杂但容易忽略——Stream 是管道,真正干活的是你写的 CouponPolicy 和 CartContext。把业务规则封装进对象,再用 Stream 组织流程,才能既灵活又可靠地支撑多变的电商优惠策略。

















