Finding Statistics about Numerical Streams in Java

Java 8 provides classes called IntSummaryStatistics, DoubleSummaryStatistics and LongSummaryStatistics which give a state object for collecting statistics such as count, min, max, sum, and average.

Version ≥ Java SE 8
List naturalNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = naturalNumbers.stream()
                                      .mapToInt((x) -> x)
                                      .summaryStatistics();
System.out.println(stats);

Which will result in:

Version ≥ Java SE 8
IntSummaryStatistics{count=10, sum=55, min=1, max=10, average=5.500000}

Converting an iterator to a stream

Use Spliterators.spliterator() or Spliterators.spliteratorUnknownSize() to convert an iterator to a stream:

Iterator iterator = Arrays.asList("A", "B", "C").iterator();
Spliterator spliterator = Spliterators.spliteratorUnknownSize(iterator, 0);
Stream stream = StreamSupport.stream(spliterator, false);

Using IntStream to iterate over indexes

Streams of elements usually do not allow access to the index value of the current item. To iterate over an array or ArrayList while having access to indexes, use IntStream.range(start, endExclusive).

String[] names = { "Jon", "Darin", "Bauke", "Hans", "Marc" };
IntStream.range(0, names.length)
    .mapToObj(i -> String.format("#%d %s", i + 1, names[i]))
    .forEach(System.out::println);

The range(start, endExclusive) method returns another ÌntStream and the mapToObj(mapper) returns a stream of String.

Output:

#1 Jon
#2 Darin
#3 Bauke
#4 Hans
#5 Marc

This is very similar to using a normal for loop with a counter, but with the benefit of pipelining and parallelization:

for (int i = 0; i < names.length; i++) {
String newName = String.format(“#%d %s”, i + 1, names[i]);
System.out.println(newName);
}

Concatenate Streams

Variable declaration for examples:

Collection abc = Arrays.asList("a", "b", "c");
Collection digits = Arrays.asList("1", "2", "3");
Collection greekAbc = Arrays.asList("alpha", "beta", "gamma");

Example 1 – Concatenate two Streams

final Stream concat1 = Stream.concat(abc.stream(), digits.stream());

concat1.forEach(System.out::print);
// prints: abc123

Example 2 – Concatenate more than two Streams

final Stream concat2 = Stream.concat(
Stream.concat(abc.stream(), digits.stream()),
greekAbc.stream());
System.out.println(concat2.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma

Alternatively to simplify the nested concat() syntax the Streams can also be concatenated with flatMap():

final Stream concat3 = Stream.of(
abc.stream(), digits.stream(), greekAbc.stream())
.flatMap(s -> s);
// or .flatMap(Function.identity());(java.util.function.Function)

System.out.println(concat3.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma

Be careful when constructing Streams from repeated concatenation, because accessing an element of a deeply concatenated Stream can result in deep call chains or even a StackOverflowException

Related Article: Using Streams and Method References in Java

Reduction with Streams

Reduction is the process of applying a binary operator to every element of a stream to result in one value.

The sum() method of an IntStream is an example of a reduction; it applies addition to every term of the Stream, resulting in one final value:

Reduction with Streams

This is equivalent to (((1+2)+3)+4)

The reduce method of a Stream allows one to create a custom reduction. It is possible to use the reduce method to implement the sum() method:

IntStream istr;
//Initialize istr
OptionalInt istr.reduce((a,b)->a+b);

The Optional version is returned so that empty Streams can be handled appropriately. Another example of reduction is combining a Stream> into a single LinkedList:

Stream> listStream;
//Create a Stream>
Optional> bigList = listStream.reduce((LinkedList list1,      LinkedList list2)->{
    LinkedList retList = new LinkedList();
    retList.addAll(list1);
    retList.addAll(list2);
    return retList;
});

You can also provide an identity element. For example, the identity element for addition is 0, as x+0==x. For multiplication, the identity element is 1, as x*1==x. In the case above, the identity element is an empty LinkedList, because if you add an empty list to another list, the list that you are “adding” to doesn’t change:

Stream> listStream;

//Create a Stream>

LinkedList bigList = listStream.reduce(new LinkedList(), (LinkedList list1, LinkedList
list2)->{
     LinkedList retList = new LinkedList();
     retList.addAll(list1);
     retList.addAll(list2);
     return retList;
});

Note that when an identity element is provided, the return value is not wrapped in an Optional—if called on an empty stream, reduce() will return the identity element.

The binary operator must also be associative, meaning that (a+b)+c==a+(b+c). This is because the elements may be reduced in any order. For example, the above addition reduction could be performed like this:

Reduction with Streams

This reduction is equivalent to writing ((1+2)+(3+4)). The property of associativity also allows Java to reduce the Stream in parallel—a portion of the Stream can be reduced by each processor, with a reduction combining the result of each processor at the end.

Using Streams of Map.Entry to Preserve Initial Values after Mapping

When you have a Stream you need to map but want to preserve the initial values as well, you can map the Stream to a Map.Entry using a utility method like the following:

public static Function> entryMapper(Function mapper){
return (k)->new AbstractMap.SimpleEntry<>(k, mapper.apply(k));
}

Then you can use your converter to process Streams having access to both the original and mapped values:

Set mySet;
Function transformer = SomeClass::transformerMethod;
Stream> entryStream = mySet.stream()
.map(entryMapper(transformer));

You can then continue to process that Stream as normal. This avoids the overhead of creating an intermediate collection.

IntStream to String

Java does not have a Char Stream, so when working with Strings and constructing a Stream of Characters, an option is to get a IntStream of code points using String.codePoints() method. So IntStream can be obtained as below:

public IntStream stringToIntStream(String in) {
return in.codePoints();
}

It is a bit more involved to do the conversion other way around i.e. IntStreamToString. That can be done as follows:

public String intStreamToString(IntStream intStream) {
     return intStream.collect(StringBuilder::new, StringBuilder::appendCodePoint,
StringBuilder::append).toString();
}

Finding the First Element that Matches a Predicate

It is possible to find the first element of a Stream that matches a condition.

For this example, we will find the first Integer whose square is over 50000.

IntStream.iterate(1, i -> i + 1) // Generate an infinite stream 1,2,3,4…
       .filter(i -> (i*i) > 50000) // Filter to find elements where the square is >50000
       .findFirst(); // Find the first filtered element

This expression will return an OptionalInt with the result.

Note that with an infinite Stream, Java will keep checking each element until it finds a result. With a finite Stream, if Java runs out of elements but still can’t find a result, it returns an empty OptionalInt.

LEAVE A REPLY

Please enter your comment!
Please enter your name here