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 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 IntStream.

Let's start by looking at the of function.

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

With rangeand 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 iterator function.

IntStream.iterate(0, i -> i + 2).limit(3);  
// > 0, 2, 4

With 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 generate function.

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

The 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 boxed.

Stream<Integer> stream = IntStream.range(1, 5).boxed();  

You'll also find map functions that returns DoubleStream and LongStream.

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, noneMatch and 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 OptionalInt.

OptionalInt, like 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 reduce function

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.

I hope this brief introduction to IntStreams gave you some ideas about how to use this higher level way of working with int primitives in your code. There are more useful functions not mentioned in this post, so make sure to checkout the IntStream documentation.

Enjoyed the post?

If you don't want to miss future posts, make sure to subscribe