1. 概述

本文将详细介绍如何将 List 分割成指定大小的子列表。这个看似简单的操作,在 Java 标准集合 API 中竟然没有直接支持。幸运的是,GuavaApache Commons Collections 这两个主流工具库都提供了类似的实现方案。

⚠️ 本文属于 Baeldung 网站的"Java 基础回顾"系列文章,面向有经验的开发者,基础概念将简略带过。

2. 使用 Guava 分割 List

Guava 通过 Lists.partition 方法提供了简洁的 List 分割功能:

@Test
public void givenList_whenParitioningIntoNSublists_thenCorrect() {
    List<Integer> intList = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8);
    List<List<Integer>> subSets = Lists.partition(intList, 3);

    List<Integer> lastPartition = subSets.get(2);
    List<Integer> expectedLastPartition = Lists.<Integer> newArrayList(7, 8);
    assertThat(subSets.size(), equalTo(3));
    assertThat(lastPartition, equalTo(expectedLastPartition));
}

关键点说明:

  • ✅ 分割大小为 3,原列表被分成 3 个子列表
  • ✅ 最后一个子列表包含剩余元素(7,8)
  • ✅ 子列表数量 = ceil(原列表大小 / 分割大小)

3. 使用 Guava 分割 Collection

Guava 同样支持对任意 Collection 进行分割:

@Test
public void givenCollection_whenParitioningIntoNSublists_thenCorrect() {
    Collection<Integer> intCollection = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8);

    Iterable<List<Integer>> subSets = Iterables.partition(intCollection, 3);

    List<Integer> firstPartition = subSets.iterator().next();
    List<Integer> expectedLastPartition = Lists.<Integer> newArrayList(1, 2, 3);
    assertThat(firstPartition, equalTo(expectedLastPartition));
}

⚠️ 重要特性:分割后的子列表是原始集合的视图,这意味着:

@Test
public void givenListPartitioned_whenOriginalListIsModified_thenPartitionsChangeAsWell() {
    // Given
    List<Integer> intList = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8);
    List<List<Integer>> subSets = Lists.partition(intList, 3);

    // When
    intList.add(9);

    // Then
    List<Integer> lastPartition = subSets.get(2);
    List<Integer> expectedLastPartition = Lists.<Integer> newArrayList(7, 8, 9);
    assertThat(lastPartition, equalTo(expectedLastPartition));
}

💡 踩坑提示:修改原始列表会影响所有分割结果,这在并发场景下可能导致意外行为!

4. 使用 Apache Commons Collections 分割 List

Apache Commons Collections 在较新版本中也添加了类似功能:

@Test
public void givenList_whenParitioningIntoNSublists_thenCorrect() {
    List<Integer> intList = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8);
    List<List<Integer>> subSets = ListUtils.partition(intList, 3);

    List<Integer> lastPartition = subSets.get(2);
    List<Integer> expectedLastPartition = Lists.<Integer> newArrayList(7, 8);
    assertThat(subSets.size(), equalTo(3));
    assertThat(lastPartition, equalTo(expectedLastPartition));
}

与 Guava 的区别:

  • ❌ 不支持直接分割原始 Collection(仅支持 List)
  • ✅ 同样返回原始 List 的视图(修改会影响分割结果)

5. 使用 Java 8 分割 List

5.1. Collectors.partitioningBy

使用 Collectors.partitioningBy() 将列表分成两部分:

@Test
public void givenList_whenParitioningIntoSublistsUsingPartitionBy_thenCorrect() {
    List<Integer> intList = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8);

    Map<Boolean, List<Integer>> groups = 
      intList.stream().collect(Collectors.partitioningBy(s -> s > 6));
    List<List<Integer>> subSets = new ArrayList<List<Integer>>(groups.values());

    List<Integer> lastPartition = subSets.get(1);
    List<Integer> expectedLastPartition = Lists.<Integer> newArrayList(7, 8);
    assertThat(subSets.size(), equalTo(2));
    assertThat(lastPartition, equalTo(expectedLastPartition));
}

特点:

  • ✅ 基于条件分割(本例:>6 和 <=6)
  • ✅ 分割结果独立于原始列表(修改不影响子列表)
  • ❌ 只能分成两部分

5.2. Collectors.groupingBy

使用 Collectors.groupingBy() 实现多组分割:

@Test
public final void givenList_whenParitioningIntoNSublistsUsingGroupingBy_thenCorrect() {
    List<Integer> intList = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8);

    Map<Integer, List<Integer>> groups = 
      intList.stream().collect(Collectors.groupingBy(s -> (s - 1) / 3));
    List<List<Integer>> subSets = new ArrayList<List<Integer>>(groups.values());

    List<Integer> lastPartition = subSets.get(2);
    List<Integer> expectedLastPartition = Lists.<Integer> newArrayList(7, 8);
    assertThat(subSets.size(), equalTo(3));
    assertThat(lastPartition, equalTo(expectedLastPartition));
}

核心技巧:

  • 分组函数 (s - 1) / 3 实现每 3 个元素一组
  • 结果:
    • 0: [1,2,3]
    • 1: [4,5,6]
    • 2: [7,8]

5.3. 按分隔符分割列表

通过指定分隔符元素分割列表:

@Test
public void givenList_whenSplittingBySeparator_thenCorrect() {
    List<Integer> intList = Lists.newArrayList(1, 2, 3, 0, 4, 5, 6, 0, 7, 8);

    int[] indexes = 
      Stream.of(IntStream.of(-1), IntStream.range(0, intList.size())
      .filter(i -> intList.get(i) == 0), IntStream.of(intList.size()))
      .flatMapToInt(s -> s).toArray();
    List<List<Integer>> subSets = 
      IntStream.range(0, indexes.length - 1)
               .mapToObj(i -> intList.subList(indexes[i] + 1, indexes[i + 1]))
               .collect(Collectors.toList());

    List<Integer> lastPartition = subSets.get(2);
    List<Integer> expectedLastPartition = Lists.<Integer> newArrayList(7, 8);
    assertThat(subSets.size(), equalTo(3));
    assertThat(lastPartition, equalTo(expectedLastPartition));
}

实现原理:

  1. 找到所有分隔符(0)的索引
  2. 在索引位置进行 subList 切割
  3. 结果:
    • [1,2,3]
    • [4,5,6]
    • [7,8]

6. 总结

方案 优点 缺点 适用场景
Guava 简单直接,支持Collection 需要额外依赖 通用场景
Apache Commons API 类似 Guava 仅支持 List 已使用 Commons 的项目
Java 8 groupingBy 无需依赖,灵活分组 代码稍复杂 需要自定义分组逻辑
Java 8 partitioningBy 简单二分法 仅限两组 布尔分类场景

💡 实战建议:

  • 项目已用 Guava/Commons → 直接用库方法
  • 纯 Java 项目 → 优先考虑 groupingBy 方案
  • 需要高性能分割 → 注意 Guava/Commons 的视图特性

所有示例代码可在 GitHub 项目 中获取(Maven 项目,可直接导入运行)。


原始标题:Partition a List in Java | Baeldung