1. 概要

In this article, we’ll illustrate how to split a List into several sublists of a given size.

For a relatively simple operation, there’s surprisingly no support in the standard Java collection APIs. 幸い、GuavaApacheCommonsCollectionsの両方が同様の方法で操作を実装しています。

この記事は、ここBaeldungの「Java –BacktoBasic」シリーズの一部です。

2. Guavaを使用してリストを分割する

Guava facilitates partitioning the List into sublists of a specified size via the Lists.partition operation:

@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. Guavaを使用してコレクションを分割する

コレクションのパーティション化はGuavaでも可能です。

@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));
}

Keep in mind that the partitions are sublist views of the original collection, which means that changes in the original collection will be reflected in the partitions:

@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. ApacheCommonsCollectionsを使用してリストを分割する

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));
}

Commons Collections doesn’t have a corresponding option to partition a raw Collection similar to the Guava Iterables.partition.

Finally, the same caveat applies here as well: the resulting partitions are views of the original List.

5. Java8を使用してリストを分割する

Now let’s see how to use Java8 to partition our List.

5.1. コレクターpartitioningBy

We can use Collectors.partitioningBy() to split the list into 2 sublists:

@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));
}

Note: The resulting partitions aren’t a view of the main List, so any changes happening to the main List won’t affect the partitions.

5.2. コレクターgroupingBy

We can also use Collectors.groupingBy() to split our list into multiple partitions:

@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));
}

Note: Just as with Collectors.partitioningBy(), the resulting partitions won’t be affected by changes in the main List.

5.3. セパレータでリストを分割する

Java8を使用して、リストを区切り文字で分割することもできます。

@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));
}

Note: We used “0” as separator. We first obtained the indices of all “0” elements in the List, and then we split the List on these indices.

6. 結論

The solutions presented here make use of additional libraries, namely Guava and the Apache Commons Collections. Both of these are very lightweight and extremely useful overall, so it makes perfect sense to have one of them on the classpath. However, if that’s not an option, a Java only solution is shown here.

The implementation of all these examples and code snippets can be found over on GitHub. This is a Maven-based project, so it should be easy to import and run as it is.