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 在内存使用上的差距:

ints

*Image extracted from https://www.eclipse.org/collections/#concept

从图中可以看出,原始类型集合在存储密集型数据时优势明显。

✅ 总结选择 Eclipse Collections 的三大理由:

  • 性能更高:避免装箱开销
  • 内存更省:直接存原始值,不存对象头和引用
  • API 丰富:提供针对每种 primitive 的专用集合实现

🔔 补充说明:目前标准 JDK 尚未原生支持 primitive 集合,但未来可通过 Project ValhallaJEP 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
}

💡 addToValuegetIfAbsentPut 是典型“原子操作”,避免了先查后改的竞态,简洁又高效。


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+ 提供了 IntStreamDoubleStream 等原始流,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 表达力上做了大幅优化。

对于处理大量数值型数据的场景(如统计、金融计算、大数据中间层),使用 IntListLongMap 等类型能带来显著收益。

💡 项目地址:https://github.com/eugenp/tutorials/tree/master/libraries-primitive(示例代码已同步更新)

📌 建议:在性能敏感模块中优先考虑 Eclipse Collections,尤其是当你发现 List<Integer> 成为瓶颈时——是时候换条更快的路了。


原始标题:Primitive Collections in Eclipse Collections