A Stream is a sequence of elements upon which sequential and parallel aggregate operations can be performed. Any given Stream can potentially have an unlimited amount of data flowing through it. As a result, data received from a Stream is processed individually as it arrives, as opposed to performing batch processing on the data altogether. When combined with lambda expressions they provide a concise way to perform operations on sequences of data using a functional approach.

Example: (see it work on Ideone)

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");

fruitStream.filter(s -> s.contains("a"))
           .map(String::toUpperCase)
           .sorted()
           .forEach(System.out::println);

Output:

APPLE

BANANA

ORANGE

PEAR

The operations performed by the above code can be summarized as follows:

  1. Create a Stream<String> containing a sequenced ordered Stream of fruit String elements using the static factory method [Stream.of(values)](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#of-T...-).
  2. The filter() operation retains only elements that match a given predicate (the elements that when tested by the predicate return true). In this case, it retains the elements containing an "a". The predicate is given as a lambda expression.
  3. The map() operation transforms each element using a given function, called a mapper. In this case, each fruit String is mapped to its uppercase String version using the method-reference String::toUppercase.

Note that the map() operation will return a stream with a different generic type if the mapping function returns a type different to its input parameter. For example on a Stream<String> calling .map(String::isEmpty) returns a Stream<Boolean>

  1. The sorted() operation sorts the elements of the Stream according to their natural ordering (lexicographically, in the case of String).
  2. Finally, the forEach(action) operation performs an action which acts on each element of the Stream, passing it to a Consumer. In the example, each element is simply being printed to the console. This operation is a terminal operation, thus making it impossible to operate on it again.

Note that operations defined on the Stream are performed because of the terminal operation. Without a terminal operation, the stream is not processed. Streams can not be reused. Once a terminal operation is called, the Stream object becomes unusable.

Operations (as seen above) are chained together to form what can be seen as a query on the data.


Closing Streams