Java 8 Streams

What is Stream in Java ?

Stream in Java is a sequence of elements on which operations can be performed. Stream operations can be intermediate or terminal. This definition sounds very similar to exisiting Collections.

What is difference between Collections and Streams

Collections is an in memory data structure which holds all the elements in the memory. In Stream the elements are computed on demand and the values are passed to next stream or returned. In Collection, the entire collection has to be computed to pass on to the next collection, whereas stream passes elements as it computes to the next Stream thus allowing Stream not to store the complete data set im memory.

Stream operations are terminal or intermediate and are combined to form stream pipelines. A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map and a terminal operation such as Stream.forEach or Stream.reduce. Terminal operations return a result of certain type and intermediate returns the Stream itself allowing to have multiple streams in a chain. Most of the stream operations take some kind of lambda expressions.

Types of Streams

Streams can be created from different sources and collections such as List and Set. The classes Stream, IntStream, LongStream, and DoubleStream are streams over objects and the primitive int, long and double types.

Streams can be generated:

  • From collections
    Arrays.asList("a1", "a2", "a3")
        .stream()
        .forEach(System.out::println)
        
        List l = new ArrayList() ;
        l.stream()
         .forEach(System.out::println)
  • Stream class static method
    Stream.of("a1", "a2", "a3")
        .stream()
        .forEach(System.out::println)
  • IntStream, DoubleStream static methods
    IntStream.range(1, 10)
        .forEach(System.out::println);

Note: forEach is a terminal function here

Mapping regular Stream to primitive Stream.

Sometimes it's useful to transform a regular object stream to a primitive stream or vice versa.

Stream.of("1", "2", "3")
  .mapToInt(Integer::parseInt)
  .average()
  .ifPresent(System.out::println);

IntStream.range(2013, 2016)
  .mapToObj(i -> "Year " + i)
  .forEach(System.out::println);

//Output
2.0

Year 2013
Year 2014
Year 2015

Stream Intermediate Functions

Intermediate functions returns the Stream itself, allowing to chain multiple such functions. Intermediate functions are lazily executed which means unless there is a terminal function append to Stream, the intermediate funciton will not be called. Some of commonly used functions:

  • filter():

    As name suggests, it is used for filtering the elements from this stream which doesn't satisy a criteria.

    IntStream.range(1,10)
    	.filter(num -> num%3 == 0)
    	.forEach(System.out::println);
    
  • map():

    Returns a stream consisting of the results of applying the given function to the elements of this stream

    Stream.of("mon", "tues", "wed", "thur", "fri", "sat", "sun")
      .map(String::toUpperCase)
      .forEach(System.out::println);
    
    //Output
    MON
    TUES
    WED
    THUR
    FRI
    SAT
    SUN
    
    //using filter with map
    Stream.of("mon", "tues", "wed", "thur", "fri", "sat", "sun")
      .map(String::toUpperCase)
      .filter(s -> s.contains("T"))
      .forEach(System.out::println);
    //OUTPUT
    TUES
    THUR
    SAT
    One thing to note is that the ordering in which the functions are defined matter and execution takes place in the similar manner.
  • sorted(): Returns the stream in a sorted manner.
    Stream.of("mon", "tues", "wed", "thur", "fri", "sat", "sun")
      .map(String::toUpperCase) 
      .sorted()
      .forEach(System.out::println);
    //Output
    FRI
    MON
    SAT
    SUN
    THUR
    TUES
    WED

Stream Terminal Functions

  • collect(): Used to convert the stream elements to collections
    Stream.of("mon", "tues", "wed", "thur", "fri", "sat", "sun")
      .map(String::toUpperCase)
      .collect(Collectors.toList())
    //to Set
    Stream.of("mon", "tues", "wed", "thur", "fri", "sat", "sun")
      .map(String::toUpperCase)
      .collect(Collectors.toSet())
  • forEach(): Performs an action for each element of this stream.
    Stream.of("mon", "tues", "wed", "thur", "fri", "sat", "sun")
      .map(String::toUpperCase) 
      .sorted()
      .forEach(System.out::println);
    //Output
    FRI
    MON
    SAT
    SUN
    THUR
    TUES
    WED
  • count(), sum(), average(): Self explanatory functions.
  • anyMatch(): Returns whether any elements of this stream match the provided predicate. It may not evaluate the predicate on all elements if not necessary for determining the result.
    boolean isPresent = IntStream.range(1,10)
      .anyMatch(num -> num %3 == 0);
    System.out.println(isPresent);
    //Output
    true
  • findFirst() :Will return the first element matching the Predicate.
    int num = IntStream.range(4,10)
      .filter(n -> n%3 ==0)
      .findFirst()
      .getAsInt();
    //Output
    6
  • findAny() :Will return some element matching the Predicate The behavior of the operation is nondeterministic. This is mainly used with parallel streams.
    int num = IntStream.range(4,10)
      .filter(n -> n%3 ==0)
      .findAny()
      .getAsInt();
    //Output
    6