Java Collections集合常见误区与高效使用实战总结
谁更灵活?谁更省事?Collections集合方案实测对比
说实话,我写这篇不是因为想搞学术研究,纯粹是上周在重构一个数据管理模块时被逼的。需求很简单:页面里要展示一堆商品卡片,支持按价格、评分、上新时间排序,还能动态增删、分页加载、局部更新状态(比如“已收藏”按钮变色)。结果一翻旧代码,发现用了三种不同风格的“集合”处理方式——有手写的 class Collection,有 Lodash 的 _.groupBy + _.mapValues 套娃,还有个同事偷偷上了 Immutable.js。我当场就懵了:这玩意儿有必要搞得这么复杂吗?
于是我把这三套方案拉出来,真刀真枪跑了一遍真实业务逻辑(不是 hello world),加了 performance.now() 打点,反复改接口 mock 数据结构,折腾了两天。结论先放这儿:我日常开发 90% 的场景,直接用原生 Map + 数组方法就够了;Lodash 是那个“能救急但别当主力”的老朋友;Immutable.js?我把它从项目里删了,不是它不好,是它太重了,而我的需求根本没到那个量级。
三个方案的真实代码长啥样?
先看最轻量的——我最喜欢的写法:
class ProductCollection {
constructor(items = []) {
this.items = new Map();
items.forEach(item => this.add(item));
}
add(item) {
this.items.set(item.id, { ...item, updatedAt: Date.now() });
}
remove(id) {
this.items.delete(id);
}
update(id, partial) {
const existing = this.items.get(id);
if (existing) {
this.items.set(id, { ...existing, ...partial });
}
}
sortBy(key, order = 'asc') {
return Array.from(this.items.values()).sort((a, b) => {
const aVal = a[key];
const bVal = b[key];
if (aVal < bVal) return order === 'asc' ? -1 : 1;
if (aVal > bVal) return order === 'asc' ? 1 : -1;
return 0;
});
}
getIds() {
return Array.from(this.items.keys());
}
}
// 用法超直白:
const collection = new ProductCollection([
{ id: 1, name: 'iPhone', price: 5999, rating: 4.7 },
{ id: 2, name: 'Pixel', price: 4299, rating: 4.3 }
]);
collection.update(1, { isFavorited: true });
console.log(collection.sortBy('price', 'desc'));
这个类我没加任何依赖,纯 JS,调试时 console.log(this.items) 一眼看清所有状态,断点打在哪都顺滑。关键是我可以随时加个 toJSON() 或 exportToCSV(),不牵扯一堆不可变链式调用。
Lodash 方案:写得快,读着累
另一个项目里,同事图省事全用 Lodash 写集合操作:
import { mapValues, keyBy, orderBy, omit } from 'lodash';
const data = [
{ id: 1, name: 'iPhone', price: 5999 },
{ id: 2, name: 'Pixel', price: 4299 }
];
// 模拟“集合”行为
let collection = keyBy(data, 'id'); // → { 1: {...}, 2: {...} }
// 更新
collection = {
...collection,
1: { ...collection[1], isFavorited: true }
};
// 排序(注意:orderBy 返回新数组,不是对象)
const sorted = orderBy(Object.values(collection), ['price'], ['desc']);
// 删除
collection = omit(collection, [2]);
问题来了:每次 keyBy 后你拿到的是 plain object,没有 .size、没有 .has、不能 for…of,想遍历还得 Object.values() 套一层。更坑的是,omit 这种函数返回新对象,但你很难一眼看出 collection 是不是还“活着”——它可能被某个中间变量吃掉了,尤其在 React 的 useEffect 里嵌套多层时,我踩过三次“状态没更新”的坑,最后加了 console.trace() 才定位到是 collection 被意外覆盖了。
优点?确实写得快,groupBy 处理分类特别爽。但如果你需要频繁做“查-改-同步UI”,它比 Map 慢半拍,而且 IDE 不太能智能提示属性(毕竟 object 是 any)。
Immutable.js:理想很丰满,现实很骨感
最后一个,是那个被我删掉的 Immutable.js:
import { Map, List } from 'immutable';
let collection = Map().withMutations(map => {
map.set(1, { id: 1, name: 'iPhone', price: 5999 });
map.set(2, { id: 2, name: 'Pixel', price: 4299 });
});
// 更新
collection = collection.update(1, item => ({ ...item, isFavorited: true }));
// 排序?得转成 List 再 orderBy,再转回来
const sortedList = collection.valueSeq().toList()
.sortBy(item => item.price)
.reverse();
// 但注意:sortedList 是 List,collection 还是原来的 Map —— 你得自己管同步
这里不是黑 Immutable,它在大型协作项目、强一致性要求的后台管理页里确实有用。但我做的电商前台,用户刷一次列表平均 3 秒内就划走了,还要为每次点击多打包 35KB 的库?更别说团队新人看到 collection.getIn(['items', '1', 'price']) 就一脸问号。我们试过用它配 React.memo,结果 shallowEqual 判定失败率飙升——因为 Immutable 的对象 !== 普通对象,连 JSON.stringify 都对不上。
还有一个隐藏雷:服务端返回的数据是普通 JSON,你得手动 fromJS,一旦漏了一层嵌套(比如 API 返回了个 null),整个 collection 就崩了。我修过一次 bug,原因是后端把 tags: null 改成了 tags: [],前端没改解析逻辑,Immutable 把 null 当成空 Map 处理,导致 tags.map 直接报错。
性能?实测差距比我想象的小
我用 1000 条商品数据跑了 100 次排序+更新组合操作:
- 原生 Map + 数组方法:平均 8.2ms
- Lodash keyBy + orderBy:平均 10.7ms
- Immutable.js:平均 14.3ms(含 fromJS 解析)
差距确实存在,但对用户来说毫无感知。真正卡顿的地方从来不是集合操作,而是图片加载、React 重渲染、或者我忘了加 shouldComponentUpdate……所以别迷信“Immutable 更快”,它只是避免了引用污染,不是性能银弹。
我的选型逻辑
一句话总结:小项目、中等数据量、需要快速迭代——闭眼用 Map + class 封装;已有大量 Lodash 基础且不常改集合逻辑——继续用,但别新增;大型管理后台、多人高频并发修改同一份数据——再考虑 Immutable 或 Zustand + immer。
顺便说一句,我现在写集合类会默认加个 toJSON() 方法,方便调试和 localStorage 存储;还会加个 subscribe(callback) 简易监听(不用 Redux 那套),一行代码就能实现“只要价格变了就刷新价格组件”。这些在 Lodash 和 Immutable 里反而要绕好几道弯。
以上是我踩坑后的总结,希望对你有帮助。这个技巧的拓展用法还有很多,比如配合 SWR 做离线优先集合缓存、用 WeakMap 实现私有状态,后续会继续分享这类博客。有更优的实现方式欢迎评论区交流。
