1. Introduction
In this short article, we’ll talk about the skip() and limit() methods of the Java Stream API and highlight their similarities and differences.
Even though these two operations may look quite similar at first, they actually behave very differently and are not interchangeable. Actually, they’re complementary and can be handy when used together. Keep reading to learn more about them.
2. The skip() Method
The skip(n) method is an intermediate operation that discards the first n elements of a stream. The n parameter can’t be negative, and if it’s higher than the size of the stream, skip() returns an empty stream.
Let’s see an example:
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 + " "));
In this stream, we’re picking up the even numbers of the stream but we skip the first two. Therefore, our result is:
6 8 10
When this stream is executed, the forEach starts asking for items. When it gets to skip(), this operation knows that the first two items have to be discarded, so it doesn’t add them to the resulting stream. After that, it creates and returns a stream with the remaining items.
In order to do this, the skip() operation has to keep the state of the elements seen at each moment. For this reason, we say that skip() is a stateful operation.
3. The limit() Method
The limit(n) method is another intermediate operation that returns a stream not longer than the requested size. As before, the n parameter can’t be negative.
Let’s use it in an example:
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 + " "));
In this case, we’re picking up just two even numbers from our stream of int:
2 4
As is the case with the skip() operation, limit() is also a stateful operation since it has to keep the state of the items that are being picked up.
But unlike skip(), which consumes the entire stream, as soon as limit() reaches the maximum number of items, it doesn’t consume any more items and simply returns the resulting stream. Hence, we say that limit() is a short-circuiting operation.
When working with infinite streams, limit() can be very useful for truncating a stream into a finite one:
Stream.iterate(0, i -> i + 1) .filter(i -> i % 2 == 0) .limit(10) .forEach(System.out::println);
In this example, we’re truncating an infinite stream of numbers into a stream with only ten even numbers.
4. Combining skip() and limit()
As we mentioned earlier, the skip and limit operations are complementary, and if we combine them, they can be very helpful in some cases.
Let’s imagine that we want to modify our previous example so that it gets even numbers in batches of ten. We can do that simply by using both skip() and limit() on the same stream:
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()); }
As we can see, we can paginate through the stream quite easily with this method. Even though this is very simplistic pagination, we can see how powerful this can be when slicing a stream.
5. Conclusion
In this brief article, we’ve shown the similarities and differences of the skip() and limit() methods of the Java Stream API. We’ve also implemented some simple examples to show how we can use these methods.
As always, the full source code for the examples is available over on GitHub.