Streams in Java

Java stream API is available in java 8 and is completely different from InputStream and OutputStream of java I/O. Streams are playing a vital role to bring functional programming to java. In this tutorial you will learn the most powerful operations offered by streams API such as reduce, collect, flatMap and parallel streams. Before starting with stream API, you must have understanding of lambda expressions provided in java and i discussed in “Interfaces in Java” or “functional interfaces” or “collection methods“.

How does stream work?

A stream contains a sequence of elements and allows to perform different kind of operations on these elements. There are two type of steam operations:

Intermediate : return a stream so we can perform multiple operations and are without semicolon. In the example below filter, map and sorted are intermediate operations.

Terminal : Are void or return non-stream results. Such as forEach is a terminal operation in the example below.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

packagejava8;

import java.util.Arrays;

import java.util.List;

publicclassStreamEx{

publicstaticvoidmain(String[]args){

List myList=

Arrays.asList("A1","A2","B1","C1","C2","D1","D2");

myList

.stream()

.filter(s-&gt;s.endsWith("1"))

.map(String::toLowerCase)

.sorted()

.forEach(System.out::println);

}

}

Output

1

2

3

4

a1

b1

c1

d1

Most of the above stated operations accept some kind of lambda expression parameter using a functional interface to specify the exact behavior of the operation either stateless or non-interfering.

Non-Interfering : Does not modify the data source of the stream

Stateless : Is a deterministic operation. The state might change during execution.

Streams can be applied on different data structures such as collections, lists and sets. Stream API provides new methods stream() and parallelStream() to create sequential or parallel stream.

Different types of sequential streams

Using stream method on a list of objects returns a regular stream and we don’t necessarily need to create collections in order to work with streams. Following example is using a stream method.

1

2

3

4

5

6

7

8

List myList=Arrays.asList("A1","A2","B1","C1","C2","D1","D2");

myList

.stream()

.filter(s-&gt;s.endsWith("1"))

.map(String::toLowerCase)

.sorted()

.forEach(System.out::println);

Use Stream.of() to create a stream of object references.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

packagejava8;

import java.util.Arrays;

import java.util.List;

import java.util.stream.Stream;

publicclassStreamEx{

publicstaticvoidmain(String[]args){

List myList=Arrays.asList("A1","A2","B1","C1","C2","D1","D2");

Stream.of(myList.toArray())

.findAny()

.ifPresent(System.out::println);

}

}

To work with primitive data type, Streams provides some special operation to work with int, long and double data types.E.g IntStream, LongStream and DoubleStream.
Using IntStream

How stream operations are processed?

Intermediate operation are lazy and will be executed only when a terminal operation is present. The following example doesn’t print anything on console. The reason is terminal operation is missing.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

packagejava8;

import java.util.stream.Stream;

publicclassStreamEx{

publicstaticvoidmain(String[]args){

Stream.of("alpha","bravo","charlie")

.map(s-&gt;{

System.out.println("map: "+s);

returns.toUpperCase();

});

}

}

Adding a terminal operation, both terminal and intermediate will be executed.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

packagejava8;

import java.util.stream.Stream;

publicclassStreamEx{

publicstaticvoidmain(String[]args){

Stream.of("alpha","bravo","charlie")

.map(s-&gt;{

System.out.println("map: "+s);

returns.toUpperCase();

})

.forEach(s-&gt;System.out.println("forEach: "+s));

}

}

Output :

1

2

3

4

5

6

map:alpha

forEach:ALPHA

map:bravo

forEach:BRAVO

map:charlie

forEach:CHARLIE

The other issue with this operation is that it execute operations one after another on all elements of the stream. To avoid this behavior we can reduce the actual number of operations. In the example below if anyMatch operation returns true as the predicate applies to the given input element. Due to vertical execution of the stream, the operation will return true once element “charlie” passed. In this case map has only to be executed three time instead of mapping all the elements of stream.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

packagejava8;

import java.util.stream.Stream;

publicclassStreamEx{

publicstaticvoidmain(String[]args){

Stream.of("alpha","bravo","charlie","beta")

.map(s-&gt;{

System.out.println("map: "+s);

returns.toUpperCase();

})

.anyMatch(s-&gt;{

System.out.println("anyMatch: "+s);

returns.endsWith("E");

});

}

}

Output:

1

2

3

4

5

6

map:alpha

anyMatch:ALPHA

map:bravo

anyMatch:BRAVO

map:charlie

anyMatch:CHARLIE

Normally, a stream can’t be reused and stream get closed after a terminal operation. To overcome this limitation, a new stream chain know as stream supplier which can be used for different terminal operations and construct a new stream with all intermediate operations already set up. Follow is an example which allow us to reuse the stream for different terminal operations.