Java 8: Replace traditional for loops with IntStreams
I've previously looked at how to work on a higher level by replacing loops with streams. In this post I want to continue this by looking at
IntStream and how it could replace the traditional
for (int i=0;... loop.
IntStream is a stream of primitive int values. It's part of the
java.util.stream package, and you can find a lot of the same functions there as in the normal
Stream<T>. There also exists streams for dealing with double and long primitives, but we'll leave that out since it acts in pretty much the same way.
Creating the IntStream
There are several ways of creating an
Let's start by looking at the
IntStream.of(1, 2, 3); // > 1, 2, 3
of just takes an arbitrary number of ints, creating an
IntStream based on them.
Usually we don't want to list up all the ints like this, so let's instead continue by looking at two functions that probably will be used most of the time.
IntStream.range(1, 3); // > 1, 2 IntStream.rangeClosed(1, 3); // > 1, 2, 3
rangeClosed, we define a range of ints that we want to create a stream of.
As you probably guessed from the resulting streams, the
rangeClosed function includes the ending int, while
range excludes it.
Now what if we don't want all the ints within a range? What if we just want every even number?
Then we could use the
IntStream.iterate(0, i -> i + 2).limit(3); // > 0, 2, 4
iterator we can define a start value and a function that will calculate the next ints based on the previous element.
iterator creates an infinite stream, so I've used
limit to create a stream containing just three elements.
The last function we're going to cover for creating
IntStreams, is the
IntStream.generate(() -> ThreadLocalRandom.current().nextInt(10)).limit(3); // > 4, 1, 7
generate looks a lot like
iterator, but differ by not calculating the ints based on the previous element.
It simply takes an
IntSupplier that will independently calculate the next int.
Working with the ints
Now that we have seen how we can create an
IntStream, let's start to play with it by using some of the functions it offer.
Let's start by looking at the different map functions
First, let's use the normal
map function to find the squared value of all ints between 0 and 5.
IntStream.range(1, 5).map(i -> i * i); // > 1, 4, 9, 16
map function maps to an
IntStream, so what about when we want to return other types of streams?
Well, then we have dedicated map functions to handle these scenarios
Stream<Color> stream = IntStream.range(1, 5).mapToObj(i -> getColor(i));
mapToObject will simply return a
Stream of the type that the mapping returns.
Now, if you just want to convert an
IntStream to a
Stream<Integer>, there's a dedicated function for this job called
Stream<Integer> stream = IntStream.range(1, 5).boxed();
You'll also find map functions that returns
DoubleStream stream = IntStream.range(1, 5).mapToDouble(i -> i); LongStream stream = IntStream.range(1, 5).mapToLong(i -> i);
Moving on, let's try out some of the matching functions
Let's start by using
anyMatch to confirm that a certain range contains at least one even number.
IntStream.range(1, 5).anyMatch(i -> i % 2 == 0); // > true
anyMatch will check if a predicate holds for at least one of the elements in the stream.
We also have two other match functions,
allMatch, that should return false for this predicate.
IntStream.range(1, 5).allMatch(i -> i % 2 == 0); // > false IntStream.range(1, 5).noneMatch(i -> i % 2 == 0); // > false
Let's now use the
filter function to filter all the even numbers, then confirm that the filter did it's job by using
allMatch to make sure all numbers are even. Let's also use
noneMatch to make sure we can't find any odd numbers.
IntStream.range(1, 5) .filter(i -> i % 2 == 0) .allMatch(i -> i % 2 == 0); // > true IntStream.range(1, 5) .filter(i -> i % 2 == 0) .noneMatch(i -> i % 2 != 0); // > true
IntStream contains functions for fetching the max and min value
IntStream.range(1, 5).max().getAsInt(); // > 4 IntStream.range(1, 5).min().getAsInt(); // > 1
Both these methods and a lot of the other functions that returns one element, will return an
Optional, gives some great higher-order functions that are abstracting away the null checks. Since that's not the topic of this post, we'll just fetch the int value assuming it's there.
IntStream also offers the excellent
IntStream.range(1, 5).reduce(1, (x, y) -> x * y) // > 24
Here we reduce the stream by multiplying all the elements.
Another great thing about the Stream API, is that you can work on elements in parallel.
Let's say we have a heavy operation that we want to execute four times.
IntStream.range(1, 5).parallel().forEach(i -> heavyOperation());
parallel will execute
heavyOperation in parallel for all the elements in the stream. Thanks to this
parallel, we can potentially save a great amount of time, with very little effort.