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 示例代码(模拟地址)