Numeric StreamsJ8 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:
double[]
-->DoubleStream
int[]
-->IntStream
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 info.java8;
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:
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 info.java8;
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:
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());
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());
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());
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());
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.