User Tools

Site Tools


java_api_8

Stream API

The stream API is a step in the field of functional programming, that is a different programming style that is more declarative. It is actually much more similar to SQL than to OOP. At any rate, Java introduced the Stream API and the lambda to go in this direction.

The pipeline

A stream is a pipeline to elaborate data. It provides the normal control-flow mechanism in a different way. A stream is characterized by 3 elements:

  • a start (where we supply data),
  • a middle (elaboration),
  • an end (where we collect the data).

Begin

The beginning of the pipeline must provide the data. Streams can be obtained in a number of ways. Some examples include:

  • From a Collection via the stream() and parallelStream() methods;
  • From an array via Arrays.stream(Object[]);
  • From static factory methods on the stream classes, such as Stream.of(Object[]), IntStream.range(int, int) or Stream.iterate(Object, UnaryOperator);
  • The lines of a file can be obtained from BufferedReader.lines();
  • Streams of file paths can be obtained from methods in Files;
  • Streams of random numbers can be obtained from Random.ints();
  • Numerous other stream-bearing methods in the JDK, including BitSet.stream(), Pattern.splitAsStream(java.lang.CharSequence), and JarFile.stream().

Some examples:

String[] stringArr = {"a", "b", "c", "d"};
Stream<String> stream = Arrays.stream(stringArr); // from arrays
 
Collection<String> list = new Arrays.asList("one" , "two" , "three");
Stream<String> s1 = list.stream();                // from collections
 
Stream<String> s2 = Stream.generate(()->"generate"); // a continuos stream generated by a Supplier
                                                     // functional interface with get abstract method
 
Stream<String> s3 = Stream.iterate("0" , i -> ++(char)i);
 
Stream<Integer> s4 = Stream.iterate(0, i-> ++i);  // generates or better iterates from natural numbers. 
                                                  // It Autoboxes so it does not perform well. 
                                                  // Iterating on numbers it's better 
                                                  // done with appropriate primitive streams.
 
IntStream is5 = IntStream.iterate(0, i -> ++i);   // notice that the lambda has a pre-increment otherwise an 
                                                  // infinite stream of 0-s is generated
 
IntStream s6 = IntStream.iterate('0', x -> ++x);
		s6.limit(25).mapToObj(x->((char)x)).forEach(x->System.out.print(x + " "));

Intermediate Operations (Elaboration)

From javadoc:

Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate. Traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed.

Terminal Operation / Collection

A stream is lazily created and executed. So it won't work unless you terminate it. Termination change the stream to something more usable like a List or a Number or whatever you need. Most common terminations are:

  • collect(), it collects the result of the stream into a mutable objects as Collection
  • reduce(), reduce the stream to a single object based on some rules defined with functional interfaces.
  • find matches: they use a Predicate<? super T> to determine the match.
    • allMatch(), returns whether all elements of this stream match the provided predicate. May not evaluate the predicate on all elements if not necessary for determining the result.
    • anyMatch(), returns whether any elements of this stream match the provided predicate. May not evaluate the predicate on all elements if not necessary for determining the result. If the stream is empty then false is returned and the predicate is not evaluated.
    • noneMatch(), returns whether no elements of this stream match the provided predicate. May not evaluate the predicate on all elements if not necessary for determining the result.
  • findAny(), returns an Optional describing some element of the stream, or an empty Optional if the stream is empty.
  • findFirst(), returns an Optional describing the first element of this stream, or an empty Optional if the stream is empty. If the stream has no encounter order, then any element may be returned.
  • forEach(), iterates on the elements of the stream allowing some operation to be done, but does not return. Hangs on infinite streams.
  • min() max(), determine the min and the max value in the stream according to an optional comparator. Return an Optional. Hangs on infinite streams.
  • count(), determines the number of elements in the stream. Hangs for infinite streams.

reduce()

The signature is

T reduce(T identity, BinaryOperator<T> accumulator)

It uses a BinaryOperator functional interface to accumulate the result, by setting an initial value (identity). It has 2 inputs and 1 output of the same type. You can use it, for example to calculate the pi=3.14… with the Gregory-Leibniz series

GregoryLeibniz.java
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.*;
 
public class GregoryLeibnitz {
 
	public static void main(String[] args) {
 
		int limit = 1000;
 
 
		Double pi = 4 * IntStream.iterate(0, n -> n+1)
                              .limit(limit) // limit the infinite stream
			      .mapToDouble(v -> (double)v)
			      .reduce(0.,  //identity, i.e. the starting point
                                      (a,x) -> a + (x%2==0?1:-1) / (2*x+1) //BinaryOperator<Double>  
                                      );
		System.out.println("pi= " + pi); // 3.140592653839794
        }
}

There is another signature:

Optional<T> reduce(BinaryOperator<T> accumulator)

by which you do not pass the identity and you don't get the right type T but an Optional<T>. In this case the first value in the accumulator is the first value passed to it.

OptionalInt sum = IntStream.iterate(1, n->n+1)
				.limit(3)
				.reduce((a,x) -> a + x);
 
System.out.println("sum " + sum.toString()); // sum OptionalInt[6]
System.out.println("sum " + sum.getAsInt()); // sum 6
// notice that it sums in this way:
// x=1 a=1   // first value is the first element
// x=2 a=1+2
// x=3 a=3+3

A last option is:

<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)

Performs a reduction on the elements of this stream, using the provided identity, accumulation and combining functions. Generally used with parallel streams, it is like splitting the reduction in 2 steps. The identity value must be an identity for the combiner function. This means that for all u, combiner(identity, u) is equal to u. Additionally, the combiner function must be compatible with the accumulator function; for all u and t, the following must hold:

   combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)

collect()

The javadoc is pretty explanatory of the first signature:

<R> R collect(Supplier<R> supplier,
              BiConsumer<R,? super T> accumulator,
              BiConsumer<R,R> combiner)

This produces a result equivalent to:

     R result = supplier.get();
     for (T element : this stream)
         accumulator.accept(result, element);
     return result;

So with the supplier you create an object where to store the result (e.g. an ArrayList), the accumulator calls the functional interface that is a BiConsumer and gets 2 elements and combines them (e.g. .add() in an ArrayList). Lastly, if the operation is performed in parallel the combiner combines the result together in another type R container by using its accept method.

For example, the following will accumulate strings into an ArrayList:

List<String> asList = stringStream.collect(ArrayList::new, 
                                           ArrayList::add,
                                           ArrayList::addAll);

The following will take a stream of strings and concatenates them into a single string:

String concat = stringStream.collect(StringBuilder::new, 
                                     StringBuilder::append,
                                     StringBuilder::append)
                            .toString();

Example

StreamExercise.java
import java.util.*;
import java.util.stream.*;
 
public class StreamExercise {
 
	public static void main(String[] args) {
		// calculate the sum of an array
 
		List<Integer> list = Arrays.asList(0,1,2,3);
		int thesum = list.stream()                 //create the stream
                                 .reduce(0,(a,x)->a+x);    // elaborate and return
		System.out.println(thesum);                // 6
 
                // data from a Supplier
		Stream<Integer> s = Stream.iterate(0, n -> n+1);
		Integer i = s.limit(4)
			     .reduce(0, (a,x) -> a + x  );
 
		System.out.println(i);                     // 6
 
 
	}
 
}
java_api_8.txt · Last modified: 2016/11/02 15:24 by 178.237.8.52