Долго валялось в закладках на SO — и пригодилось, наконец. Обычно — или уже была «в коробке» Guava с Lists.partition(), или — Apache Commons и его ListUtils.partition().
Деление на подмножества (каплю доработано относительно исходника на SO):
// https://stackoverflow.com/a/30072617
public static <T> Stream<List<T>> listPartition(@NotNull List<T> source, @Positive int length) {
requireNonNull(source, "Source list must not be null");
if (length <= 0) {
throw new IllegalArgumentException("Partitions length must be greater than zero");
}
int size = source.size();
if (size == 0) {
return Stream.empty();
}
if (size <= length) {
return Stream.of(source);
}
int fullChunks = (size - 1) / length;
return IntStream.range(0, fullChunks + 1)
.mapToObj(n -> source.subList(n * length, n == fullChunks ? size : (n + 1) * length));
}
Несколько тестов
@DisplayName("Split list to two partitions")
@Test
void listPartition_1() {
List<Integer> source = List.of(1, 2, 3);
var partitions = listPartition(source, 2).collect(Collectors.toList());
assertThat(partitions).containsExactlyElementsOf(List.of(List.of(1, 2), List.of(3)));
}
@DisplayName("Split list to one partition - source size lesser than or equal to partition length")
@ParameterizedTest
@ValueSource(ints = {3, 4})
void listPartition_2(int length) {
List<Integer> source = List.of(1, 2, 3);
var partitions = listPartition(source, length).collect(Collectors.toList());
assertThat(partitions).containsExactlyElementsOf(List.of(List.of(1, 2, 3)));
}
@DisplayName("Empty result - source is empty")
@Test
void listPartition_3() {
List<Integer> source = List.of();
var partitions = listPartition(source, 2).collect(Collectors.toList());
assertThat(partitions).isEmpty();
}
@DisplayName("Thrown IAE - wrong 'length' arg passed")
@ParameterizedTest
@ValueSource(ints = {-1, 0})
void listPartition_4(int length) {
List<Integer> source = List.of();
assertThatThrownBy(() -> listPartition(source, length))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Partitions length must be greater than zero");
}
@DisplayName("Thrown NPE - source list is null")
@Test
void listPartition_5() {
List<Integer> source = null;
assertThatThrownBy(() -> listPartition(source, 2))
.isInstanceOf(NullPointerException.class)
.hasMessage("Source list must not be null");
}