1. 简介

在本篇文章中,我们将深入讲解 Java Stream API 中的两个常用方法:skip()limit(),并对比它们的相似点与差异点。

虽然这两个方法看起来很相似,但它们的行为和用途其实大不相同。它们一个用于跳过元素,一个用于限制数量,配合使用时可以实现流的“切片”操作,非常适合用于分页或批量处理场景。

2. skip() 方法详解

skip(n) 是一个中间操作(intermediate operation),用于跳过流中的前 n 个元素。如果 n 大于流的长度,则返回一个空流。

⚠️注意:n 不能为负数。

来看一个例子:

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    .filter(i -> i % 2 == 0)
    .skip(2)
    .forEach(i -> System.out.print(i + " "));

输出结果为:

6 8 10

✅说明:我们筛选出偶数,然后跳过前两个(即 2 和 4),最终输出的是 6、8、10。

skip() 是有状态操作(stateful)

由于 skip() 需要记住已经跳过了多少个元素,因此它是一个有状态操作,在处理流的过程中会维护内部状态。

3. limit() 方法详解

limit(n) 也是一个中间操作,用于限制流的大小,只保留前 n 个元素。

来看一个例子:

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    .filter(i -> i % 2 == 0)
    .limit(2)
    .forEach(i -> System.out.print(i + " "));

输出结果为:

2 4

✅说明:我们筛选出偶数,并限制只取前两个,因此结果是 2 和 4。

limit() 是短路操作(short-circuiting)

一旦取到了 n 个元素,limit() 就会立即停止处理后续元素,因此它是一个短路操作

这对于处理无限流(infinite stream)非常有用。例如:

Stream.iterate(0, i -> i + 1)
    .filter(i -> i % 2 == 0)
    .limit(10)
    .forEach(System.out::println);

这段代码会输出前 10 个偶数,虽然 Stream.iterate 本身是无限流,但 limit(10) 限制了最终只取 10 个元素。

4. skip() 与 limit() 联合使用

这两个方法配合使用可以实现流的“分页”效果,非常适用于数据分页或批量处理的场景。

例如,我们可以实现一个方法,用于获取偶数流中某个“页”的数据:

private static List<Integer> getEvenNumbers(int offset, int limit) {
    return Stream.iterate(0, i -> i + 1)
        .filter(i -> i % 2 == 0)
        .skip(offset)
        .limit(limit)
        .collect(Collectors.toList());
}

调用示例:

List<Integer> page1 = getEvenNumbers(0, 10); // 获取前10个偶数
List<Integer> page2 = getEvenNumbers(10, 10); // 获取第11到20个偶数

✅说明:skip(offset) 用于跳过前面的元素,limit(limit) 控制每页取多少个元素,组合起来实现分页功能。

⚠️踩坑提醒:在并行流中使用 skip()limit() 时要格外小心,因为它们的行为可能不如预期高效,尤其是 skip() 在无序流中可能无法正确跳过指定数量的元素。

5. 总结

特性 skip(n) limit(n)
类型 中间操作 中间操作
行为 跳过前 n 个元素 只保留前 n 个元素
是否短路
是否有状态
适用场景 分页、批量处理 分页、截断无限流

这两个方法在 Java Stream API 中非常实用,尤其是在处理数据流的切片和分页时。建议结合使用,以实现更灵活的流处理逻辑

完整的示例代码已上传至 GitHub,如需参考可自行 clone:Java 8 Stream 示例代码(模拟地址)


原始标题:Java 8 Stream skip() vs limit()