如何在Java 8中对对象列表进行分页?

时间:2015-03-26 08:13:45

标签: java list java-8 partition

如果java.util.List包含n元素且所需的网页尺寸m,我想将其转换为包含n/m+n%m元素的地图。每个地图元素都应包含m个元素。

以下是整数示例:

    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    // What is the equivalent Java 8 code to create the map below from my list?

    Map<Integer, List<Integer>> map = new HashMap<>();
    map.put(0, Arrays.asList(1,2,3));
    map.put(1, Arrays.asList(4,5,6));
    map.put(2, Arrays.asList(7,8,9));
    map.put(3, Arrays.asList(10));

这可能,使用Java 8吗?

3 个答案:

答案 0 :(得分:11)

您可以在IntStream.iterate上使用toMapsubList收集器和List方法结合使用(感谢Duncan进行简化)。

import static java.util.stream.Collectors.toMap;
import static java.lang.Math.min;

...

static Map<Integer, List<Integer>> partition(List<Integer> list, int pageSize) {
    return IntStream.iterate(0, i -> i + pageSize)
          .limit((list.size() + pageSize - 1) / pageSize)
          .boxed()
          .collect(toMap(i -> i / pageSize,
                         i -> list.subList(i, min(i + pageSize, list.size()))));
}

首先计算地图中所需的键数。这由(list.size() + pageSize - 1) / pageSize给出(这将是流的限制)。

然后创建一个创建序列0, pageSize, 2* pageSize, ...的Stream。

现在,对于每个值i,您将获取相应的subList,这将是我们的值(您需要额外检查最后subList是否超出范围)映射相应的键,该键将是您0/pageSize, pageSize/pageSize, 2*pageSize/pageSize除以的序列pageSize,以获得自然序列0, 1, 2, ...

管道可以安全地并行运行(您可能需要使用toConcurrentMap收集器)。正如Brian Goetz评论的那样(感谢提醒我),如果你想要并行化流,iterate是不值得的,所以这里的版本是range

return IntStream.range(0, (list.size() + pageSize - 1) / pageSize)
                .boxed()
                .collect(toMap(i -> i ,
                               i -> list.subList(i * pageSize, min(pageSize * (i + 1), list.size()))));

与您的示例(页面大小为3的10个元素的列表)一样,您将得到以下序列:

0, 3, 6, 9, 12, 15, ...您限制为(10 + 3 - 1) / 3 = 12 / 3 = 4,其中包含序列0, 3, 6, 9。现在每个值都映射到相应的子列表:

0 / pageSize = 0 -> list.subList(0, min(0 + pageSize, 10)) = list.subList(0, 3);
3 / pageSize = 1 -> list.subList(3, min(3 + pageSize, 10)) = list.subList(3, 6);
6 / pageSize = 2 -> list.subList(6, min(6 + pageSize, 10)) = list.subList(6, 9);
9 / pageSize = 3 -> list.subList(9, min(9 + pageSize, 10))  = list.subList(6, 10);
                                      ^
                                      |
                        this is the edge-case for the last sublist to
                        not be out of bounds

<小时/> 如果您真的想要Map<Integer, String>,可以用

替换值映射器函数
import static java.util.stream.Collectors.joining;

...

i -> list.subList(i, min(i + pageSize, list.size()))
         .stream()
         .map(Object::toString)
         .collect(joining(","))

它只是将逗号分隔的元素收集到一个String中。

答案 1 :(得分:4)

使用Guava的简单解决方案:com.google.common.collect.Lists#partition

    List<List<Integer>> partition = Lists.partition(list, 3); //<- here
    Map map = IntStream.range(0, partition.size()).boxed().collect(Collectors.toMap(
                    Function.identity(),
                    i -> partition.get(i)));

答案 2 :(得分:0)

如评论中所述,如果列表不是自然的整数序列,这也有效。您必须使用生成的IntStream然后按索引引用列表中的元素。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

Map<Integer, String> map = IntStream
    .range(0, list.size())
    .boxed()
    .collect(groupingBy(
        i -> i / 3, //no longer i-1 because we start with 0
        mapping(i -> list.get((int) i).toString(), joining(","))
        ));

//result: {0="1,2,3", 1="4,5,6", 2="7,8,9", 3="10"}

我们从表示列表索引的IntStream开始。

groupingBy按一些分类器对元素进行分组。在您的情况下,它每页分组x个元素。

mapping将映射函数应用于元素并在之后收集它们。映射是必要的,因为joining仅接受CharSequencejoining本身通过使用任意分隔符来连接元素。