Numeric StreamsS2C Home « Numeric Streams

In the Array Type Streams lesson we got a first look at using some of the overloaded stream() methods of the Arrays class. We also learnt that if your streaming arrays of objects then the Stream.of static method and the Arrays.stream() method both end up using Arrays.stream() under the bonnet. However we found that if you're streaming primitive type arrays then using the Arrays.stream() variants that were built for this purpose is best practice as they produce streams pertinent to the input:

  1. double[] --> DoubleStream
  2. int[] --> IntStream
  3. long[] --> LongStream

In this lesson we look at these streams in greater detail along with some useful methods available in their interfaces.

Using IntStream Top

We will be using methods from the IntStream interface for our examples, the variants of these methods are available in the DoubleStream and LongStream interfaces.

Following is a TestIntStreamA class to demonstrate usage of some of the methods of the IntStream interface.


package com.server2client;

import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Collectors;


/* Create streams from arrays */
public class TestIntStreamA {

    public static void main(String[] args) {

        // Create an array and stream it
        int[] intArray = {16, 8, 3, 12, 16, 5, 1, 2, 12};
        IntStream intStream = Arrays.stream(intArray);
        intStream.forEach(System.out::print);

        // Add up distinct numbers
        IntStream intStream2 = Arrays.stream(intArray);
        long total = intStream2.distinct()
                .sum();
        System.out.println("\nThe distinct numbers in the array add up to: " + total);

        // Get stream statistics
        IntStream intStream3 = Arrays.stream(intArray);
        IntSummaryStatistics stats = intStream3.collect(IntSummaryStatistics::new,
                IntSummaryStatistics::accept,
                IntSummaryStatistics::combine);
        System.out.println("Statistic for this stream: " + stats);

        // Box and collect to list
        List<Integer> ints = IntStream.of(16, 8, 3, 12, 16)
                .boxed()
                .collect(Collectors.toList());
        System.out.println("Integers in List collection: " + ints);

        // Collect to array
        int intArray2[] = IntStream.of(16, 8, 3, 12, 16)
                .toArray();
        for (int j : intArray2) {
            System.out.println("int: " + j);
        }
    }
}

Building and running the TestIntStreamA class produces the following output:

Run TestIntStreamA class
Screenshot 1. Running the TestIntStreamA class.

Lets go though the code and see what's new!

We use the distinct() method which returns a stream consisting of the distinct elements of this stream and the sum()method which returns the sum of elements in this stream, for the first time.

We create an IntSummaryStatistics object from an IntStream which collects statistics such as count, min, max, sum, and average and print off these statistics.

We can't store primitives in collections as they are generic so we use the handy boxed() method to wrap some ints and use the collect method along with Collectors.toList() to store the results in a List<E> which we print off. We look at collectors in more detail in the Stream Collectors lesson.

Replacing Simple for Construct Top

There are lots of useful methods in the IntStream interface including the range() and rangeClosed() methods which can be used for replacing the simple for construct with exclusive and inclusive closing ranges.

Lets take a look.


package com.server2client;

import java.util.stream.IntStream;

/* IntStream ranges */
public class TestIntStreamB {

    public static void main(String[] args) {

        // Simple for
        for (int i=1; i < 10; i++) {
            printInt(i);
        }

        // range inclusive
        System.out.println();
        IntStream.rangeClosed(1, 9)
                .forEach(TestIntStreamB::printInt);

        // range exclusive
        System.out.println();
        IntStream.range(1, 10)
                .forEach(TestIntStreamB::printInt);
    }

    private static void printInt(int i) {
        System.out.print("i = : " + i +  ". ");
    }
}

Building and running the TestIntStreamB class produces the following output:

Run TestIntStreamB class
Screenshot 2. Running the TestIntStreamB class.

As you can see we get the same results from all three loops as we would expect.

Although limited to replacing the simple for construct when we want to loop incrementally and sequentially, you can see how simple the range() and rangeClosed() methods are compared to the simple for construct. Same results with much simpler, less error prone code.

Taking Advantage of Numeric Streams Top

Well this is all great but lets see if we can take advantage of this new knowledge when we are using object type streams instead of primitive type streams.

We will be using the Employee class we created in the Introducing Streams lesson to test against for this purpose.

The following code snippet could be used to show the average age of all employes:


/* Get average age of all staff */
double averageAge = Employee.listOfStaff().stream()
        .map(Employee::getAge)
        .reduce(0, Integer::sum);
System.out.println("Average age of staff is: "
        + averageAge / Employee.listOfStaff().size());

Run employee average age - attempt one
Screenshot 3. Stream Employee List and get average age - Attempt 1.

This produces the required result of 43.2 but is quite verbose. The code also includes some unwanted boxing of the Integer object to an int primitive before summation which could have a big impact on performance if this was a large list, so not ideal.

We used the reduce() terminal operator, for the first time, which performs a reduction on the elements of this stream, using the provided identity, accumulation and combining functions

Lets try and simplify the above code/p>


/* Get average age of all staff */
double averageAge = Employee.listOfStaff().stream()
        .map(Employee::getAge)
        .sum();
System.out.println("Average age of staff is: "
        + averageAge / Employee.listOfStaff().size());

Run employee average age - attempt two
Screenshot 4. Stream Employee List and get average age - Attempt 2.

This doesn't even compile as the output from a map is Stream<T> which doesn't have a sum() method.

Luckily for us the developers of Java had the foresight to see this type of situation and introduced three stream interfaces for primitive numeric types, these being IntStream, DoubleStream and LongStream. Each have map methods that return a stream of the primitive type required.

Lets see this in action


/* Get average age of all staff */
double averageAge = Employee.listOfStaff().stream()
        .mapToDouble(Employee::getAge)
        .sum();
System.out.println("Average age of staff is: "
        + averageAge / Employee.listOfStaff().size());

Run employee average age - attempt three
Screenshot 5. Stream Employee List and get average age - Attempt 3.

This is still quite bloated but does produce the required result of 43.2.

Looking through the Java documentation for the IntStream interface I can see there is an average() method that returns an OptionalDouble object, so lets give that a whirl!


/* Get average age of all staff */
OptionalDouble averageAge = Employee.listOfStaff().stream()
        .mapToDouble(Employee::getAge)
        .average();
System.out.println("Average age of staff is: " + averageAge.getAsDouble());

Run employee average age - attempt four
Screenshot 6. Stream Employee List and get average age - Attempt 4.

Well we finally got there, this is a lot easier to read and more efficient.

We used the mapToDouble() intermediate operator, for the first time, which returns a DoubleStream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element and the .average() terminal operator which returns an OptionalDouble describing the arithmetic mean of elements of this stream, or an empty optional if this stream is empty.

It is worth getting to know your way around the IntStream, DoubleStream and LongStream interfaces as the methods within them can be very useful for creating clean efficient code.

Related Quiz

Streams Quiz 5 - Numeric Streams Quiz

Lesson 5 Complete

In this lesson we looked at numeric streams and some of the operations associated with them.

What's Next?

In the next lesson we take a final look at stream creation.