
用 HashMap 存商品 ID 到数量的映射最直接
购物车本质是「商品 ID → 购买数量」的关联关系,HashMap 天然适合:查得快(O(1))、改得快、支持动态增删。别一上来就套 ArrayList 存重复商品对象,那会导致遍历找同款、去重逻辑臃肿。
常见错误现象:NullPointerException 来自没判空就调 get() 后直接 ++;或者用 Integer 作 value 却忘了自动拆箱时可能抛 NullPointerException。
- 初始化用
new HashMap<string integer>()</string>,key 用商品 ID(String比Long更兼容数据库主键和前端传参) - 加商品时用
cart.put(productId, cart.getOrDefault(productId, 0) + 1),避免手动判空 - 减数量建议用
computeIfPresent:如果减到 0 就自动移除,保持 map 干净
商品信息不能只靠 ID,得单独维护一份 Map<string product></string>
HashMap 里只存 ID 和数量,是为了轻量和高效;但展示购物车时要显示名称、单价、小计,这些必须从另一份「商品主数据」里查。硬把完整 Product 对象塞进购物车 map,会引发两个问题:一是修改商品信息(比如调价)不会同步到已有购物车,二是序列化/传输体积大、GC 压力高。
使用场景:用户刷新页面、结算前重新拉取商品最新快照、后台导出明细报表。
- 不要在购物车 map 里存
Product实例,哪怕它看起来“方便” - 建一个只读的
Map<string product></string>缓存商品基础信息,用ConcurrentHashMap避免并发读写冲突 - 计算总价时,对购物车 map 的每个 entry,用 key 查商品 map 取单价,再乘数量 —— 这步别漏了空值检查(商品下架时 key 可能无效)
并发修改购物车必须加锁,但别锁整个 map
用户可能在多个标签页或设备上同时操作同一购物车,HashMap 本身不安全,直接用 ConcurrentHashMap 也不够 —— 它保证单个操作原子,但「先 get 再 put」这种两步操作依然会丢更新。
典型错误:两个请求同时对同一商品执行「+1」,结果只加了一次。
- 对单个商品 ID 的增删改,用
compute或merge方法,它们是原子的 - 如果涉及多商品批量操作(比如清空、全选删除),需要外部同步,但粒度控制在业务层,别用
synchronized(cart)锁住整个 map - 更稳妥的做法是把购物车操作封装成方法,内部用
ReentrantLock按用户 ID 分段加锁,避免不同用户互相阻塞
序列化购物车时别忽略泛型擦除和 null 安全
Java 序列化(比如存 Redis 或传 JSON)时,HashMap<string integer></string> 的类型信息会丢失。反序列化回来可能变成 HashMap<object object></object>,后续强转出错;另外,如果商品数量为 null(比如初始化异常),JSON 库可能直接跳过字段,导致前端看到“消失的商品”。
性能影响:用 Jackson 默认配置序列化大购物车,没配 @JsonInclude(JsonInclude.Include.NON_NULL) 会多传一堆 null 字段。
- 写 JSON 时显式指定泛型类型:用
new TypeReference<map integer>>() {}</map> - 数量字段绝不允许为 null,初始值设 0,反序列化失败时要有 fallback 日志
- Redis 存储建议用 Hash 结构(
HSET cart:123 item_001 2),比存整个 JSON 更易做原子计数和分页
最容易被忽略的是商品下架后的状态一致性:购物车里还留着已下架商品 ID,但商品 map 里查不到对应 Product。这个空洞不会报错,却会让总价计算变成 0,前端显示异常价格 —— 得在每次读购物车时做一次“存活校验”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











