1. 引言
本文将深入探讨 Java 中原始类型(primitive)集合的使用痛点,以及 Eclipse Collections 如何提供高效、内存友好的替代方案。
如果你还在用 List<Integer>
存大量整数,那这篇文章可能会让你少踩几个性能坑。
2. 为什么需要原始类型集合?
先看一段看似正常的代码:
List<Integer> myList = new ArrayList<>();
int one = 1;
myList.add(one);
虽然写起来顺手,但背后有个隐藏成本:✅ 自动装箱(boxing)。
Java 集合只能存储对象引用,所以每次添加 int
时,都会被包装成 Integer
对象。同理,取值时还要拆箱(unboxing)。
⚠️ 问题来了:频繁的装箱/拆箱不仅消耗 CPU,还增加 GC 压力。更关键的是——内存占用更高。
下图直观展示了 ArrayList<Integer>
与 Eclipse Collections 的 IntArrayList
在内存使用上的差距:
*Image extracted from https://www.eclipse.org/collections/#concept
从图中可以看出,原始类型集合在存储密集型数据时优势明显。
✅ 总结选择 Eclipse Collections 的三大理由:
- 性能更高:避免装箱开销
- 内存更省:直接存原始值,不存对象头和引用
- API 丰富:提供针对每种 primitive 的专用集合实现
🔔 补充说明:目前标准 JDK 尚未原生支持 primitive 集合,但未来可通过 Project Valhalla 和 JEP 218 实现。在此之前,Eclipse Collections 是最成熟的选择之一。
3. 依赖引入
使用 Maven 添加以下依赖:
<dependency>
<groupId>org.eclipse.collections</groupId>
<artifactId>eclipse-collections-api</artifactId>
<version>10.0.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.collections</groupId>
<artifactId>eclipse-collections</artifactId>
<version>10.0.0</version>
</dependency>
📌 建议:生产环境请锁定具体版本,避免 SNAPSHOT 版本引发兼容性问题。
4. long 类型列表
Eclipse Collections 为所有基本类型(如 int
, long
, double
等)提供了优化过的列表、集合、栈、映射和包(Bag)实现。
以 long
为例,创建一个可变的 long
列表并求和:
@Test
public void whenListOfLongHasOneTwoThree_thenSumIsSix() {
MutableLongList longList = LongLists.mutable.of(1L, 2L, 3L);
assertEquals(6, longList.sum());
}
✅ MutableLongList
直接存储 long
值,无装箱,求和操作也无需遍历转类型。
5. int 类型列表
同样支持不可变列表。比如创建一个 int
不可变列表并获取最大值:
@Test
public void whenListOfIntHasOneTwoThree_thenMaxIsThree() {
ImmutableIntList intList = IntLists.immutable.of(1, 2, 3);
assertEquals(3, intList.max());
}
📌 提示:immutable.of()
适合常量或配置数据;若需修改,用 mutable.of()
。
6. 原始类型映射(Primitive Maps)
除了常规 Map 操作,Eclipse Collections 还为原始类型对提供了专属方法。
例如操作 int → int
映射:
@Test
public void testOperationsOnIntIntMap() {
MutableIntIntMap map = new IntIntHashMap();
assertEquals(5, map.addToValue(0, 5)); // key=0, value+=5 → 返回新值
assertEquals(5, map.get(0));
assertEquals(3, map.getIfAbsentPut(1, 3)); // key=1 不存在,put(1,3),返回3
}
💡 addToValue
和 getIfAbsentPut
是典型“原子操作”,避免了先查后改的竞态,简洁又高效。
7. 从 Iterable 转原始集合
Eclipse Collections 与 Iterable
完美集成,可轻松转换。
将 Iterable<Integer>
转为 int
集合:
@Test
public void whenConvertFromIterableToPrimitive_thenValuesAreEqual() {
Iterable<Integer> iterable = Interval.oneTo(3); // [1,2,3]
MutableIntSet intSet = IntSets.mutable.withAll(iterable);
IntInterval intInterval = IntInterval.oneTo(3);
assertEquals(intInterval.toSet(), intSet);
}
进一步,还能从 Iterable
构建原始类型映射:
@Test
public void whenCreateMapFromStream_thenValuesMustMatch() {
Iterable<Integer> integers = Interval.oneTo(3);
MutableIntIntMap map =
IntIntMaps.mutable.from(
integers,
key -> key,
value -> value * value); // key=id, value=square
MutableIntIntMap expected = IntIntMaps.mutable.empty()
.withKeyValue(1, 1)
.withKeyValue(2, 4)
.withKeyValue(3, 9);
assertEquals(expected, map);
}
📌 from(Iterable, keyFunc, valueFunc)
类似于 Stream 的 collect(Collectors.toMap())
,但专为原始类型设计,零装箱。
8. 原始类型的 Stream 支持
Java 8+ 提供了 IntStream
、DoubleStream
等原始流,Eclipse Collections 也能无缝对接。
示例:从 DoubleList
获取原始 DoubleStream
并计算平均值:
@Test
public void whenCreateDoubleStream_thenAverageIsThree() {
DoubleStream doubleStream = DoubleLists
.mutable.with(1.0, 2.0, 3.0, 4.0, 5.0)
.primitiveStream();
assertEquals(3, doubleStream.average().getAsDouble(), 0.001);
}
✅ .primitiveStream()
返回的是 JDK 原生的 DoubleStream
,可以直接使用所有标准 Stream 操作。
9. 结语
Eclipse Collections 的原始类型集合不仅解决了 Java 集合装箱带来的性能瓶颈,还在内存占用和 API 表达力上做了大幅优化。
对于处理大量数值型数据的场景(如统计、金融计算、大数据中间层),使用 IntList
、LongMap
等类型能带来显著收益。
💡 项目地址:https://github.com/eugenp/tutorials/tree/master/libraries-primitive(示例代码已同步更新)
📌 建议:在性能敏感模块中优先考虑 Eclipse Collections,尤其是当你发现 List<Integer>
成为瓶颈时——是时候换条更快的路了。