Quantcast
Viewing all articles
Browse latest Browse all 5

Ranges and Looping with IntStream

In the previous posts we looked at taking a container, getting it to stream its contents into a pipeline and looked at a few different operations on data in that pipeline (forEach, map, filter, peek).

Java 8 supports several specialist streams where the pipeline contains a specific type of object. Today we’ll look at IntStream which passes Integers along its pipeline.

public class IntStreamExample
{
	public static void main(String[] args)
	{
		System.out.println("[1,5]");
		IntStream.rangeClosed(1, 5).forEach(System.out::println);

		System.out.println("[1,5)");
		IntStream.range(1, 5).forEach(System.out::println);

		System.out.println("Just 3");
		IntStream.of(3).forEach(System.out::println);

		System.out.println("Specific values");
		IntStream.of(1, 3, 5, 6).forEach
                (System.out::println);

		System.out.println("[1,3] and [4,6] joined");
		IntStream.concat(IntStream.rangeClosed(1, 3),
		IntStream.rangeClosed(4, 6)).forEach(System.out::println);
	}
}

To build our stream we take the IntStream class and use one of its static methods. A selection of such methods are demonstrated in the example above.

For those who missed the previous articles, the :: operator means pass the function on the right, calling with the object on the left.

Let’s describe each of the methods:

  • The range and rangeClosed methods produce a stream which has an ordered pipeline of integers starting at the first number and ending at the second. The difference is that rangeClosed has an inclusive endpoint where are range does not. There is no version yet with a step or descending values – the pipeline is initialised as empty if the start is beyond the last element. If we wanted a step, we use transformations with map:
IntStream.rangeClosed(1, 5).map(x -> 6-x)
                           .forEach(System.out::println);

This is a bit clumsy (and hopefully a step version will be added soon) but it does the trick. If we need a step often, we could make our own based on the IntStream class.

  • The of method puts a one or more values in the pipeline. The multiple value version takes a variable number of ints (which means we can also pass an array of int).
  • Finally the concat method can be used to put two or more IntStreams together into a single IntStream.
  • Note that there also is an empty() method which produces an empty stream.

    A use of range is a functional-style for-loop. It’s functional-style because there is no mutable loop variable. The examples above have already demonstrate this – the ‘body’ of the loop just printed the loop counter.

    What if we want to nest two loops? That’s easy:

    public class Multiplication
    {
    	public static void main(String[] args)
    	{
    		IntStream.rangeClosed(1, 10)
    		         .forEach(i -> IntStream.rangeClosed(1, 10)
                             .forEach(
                j -> System.out.println(i + " * " + j + " = " + i * j)));
    	}
    }
    

    By using the forEach operation we can map each element onto a another stream. We don’t have to use the element there and then, we can use it later in the pipeline. In the example we use both streams to produce a multiplication table.

    We could also save the output of an IntStream to an array like this:

    	int[] a = IntStream.rangeClosed(1, 10).toArray();
    

    This provides us with a very handy way to initialise an array to a sequence of integers.

    The pipeline operation toArray returns an int[]. What if we want to create an array from two or more nested loops? The problem we run into in the nested version is that we can’t use toArray() in the inner loop since the inner loop is part of a map function which is expecting an int not an int[]. This means we have to use another trick:

    	int[] a = IntStream.rangeClosed(1, 10)
    			.flatMap(i -> IntStream.rangeClosed(1, 10)
                                              .map(j -> i * j))
                .toArray();
    

    Here we use flatMap. flatMap flattens a number of IntStreams (10 of them here) into pipeline elements (ints). These are then passed on to the next operation which is toArray().

    The lambda expression passed to flatMap is converted to an IntFunction and its apply function takes an int and returns an IntStream. Here is an inner-class with it implemented explicitly:

    	private static class MultiplicationTable implements
    			IntFunction<IntStream>
    	{
    		@Override
    		public IntStream apply(int value)
    		{
    			return IntStream.rangeClosed(1, 10).map(j -> value * j);
    		}
    	}
    

    using the call:

    		int[] a = IntStream.rangeClosed(1, 10)
    			               .flatMap(new MultiplicationTable())
    			               .toArray();
    

    Finally here is a version using a local function:

    public class Multiplication
    {
    	private IntStream getTable(int i)
    	{
    		return IntStream.rangeClosed(1, 10).map(j -> i * j);
    	}
    
    	public void test()
    	{
    		int[] a = IntStream.rangeClosed(1, 10).flatMap
                                                   (this::getTable)
    					       .toArray();
    
    		Arrays.stream(a).forEach(System.out::println);
    	}
    
    	public static void main(String[] args)
    	{
    		new Multiplication().test();
    	}
    }
    

    Note we also used the stream function on the Arrays helper class to print out the array. To do this we need to pass in the array into the stream function and then we can use forEach to print.

    That should give you a good start for using IntStreams. Note there are also special streams for Long and Double which you might want to take a look at.


    Image may be NSFW.
    Clik here to view.
    Image may be NSFW.
    Clik here to view.

    Viewing all articles
    Browse latest Browse all 5

    Trending Articles