编辑 - 2017年11月28日
正如用户@Emiel在评论中建议的那样,最好的方法是使用 Stream.itearate
通过一系列索引驱动列表:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int skip = 3;
int size = list.size();
int limit = size / skip + Math.min(size % skip, 1);
List<Integer> result = Stream.iterate(0, i -> i + skip)
.limit(limit)
.map(list::get)
.collect(Collectors.toList());
System.out.println(result);
这种方法没有我之前回答中的缺点(下面会陈述,出于历史原因,我决定将其保留)。
另一种方法是使用Stream.iterate()
,如下所示:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int skip = 3;
int size = list.size();
int limit = size / skip + Math.min(size % skip, 1);
List<Integer> result = Stream.iterate(list, l -> l.subList(skip, l.size()))
.limit(limit)
.map(l -> l.get(0))
.collect(Collectors.toList());
System.out.println(result);
这个想法是创建一个子列表的流,每一个子列表跳过前一个子列表的前N
个元素(在例子中N=3
)。
我们必须限制迭代次数,以便不尝试获取边界超出范围的子列表。
然后,我们将子列表映射到它们的第一个元素并收集结果。保留每个子列表的第一个元素的作用如预期一样,因为每个子列表的开始索引相对于源列表向右移动了N
个元素。
这也是有效的,因为List.sublist()
方法返回原始列表的视图,这意味着它不会为每次迭代创建一个新的List
。
编辑:过了一段时间,我了解到更好的方法是采用@sprinter的其中一种方法,因为subList()
会创建一个原始列表的包装器。这意味着流的第二个列表将是第一个列表的包装器,第三个列表将是第二个列表的包装器(已经是一个包装器!),依此类推...
虽然这对于小型到中型列表可能有效,但应注意,对于非常大的源列表,可能会创建许多包装器。这可能会变得昂贵,甚至会生成StackOverflowError
。