Java .stream()

The Java 8 Stream API is used when processing a collection of elements, such as a Set or List. .stream() presents each item in the list in sequence, whereas .parrallelStream() lets you multiple streams at once.

Stream is used as a way to iterate through a collection without using a code heavy for loop. You can call stream on an already existing list, or create a new streamed list as below:

List<Integer> testList = [1, 2, 3]
testList
   .stream()
   .forEach(System.out::println)

// OR 

Stream.of(1, 2, 3)
   .forEach(System.out::println)

Once you stream the collection, there are many powerful methods you can use.


collect

Starting at the end, once you are done streaming, you will want to put your stream back into a collection. To do this we use the collect method. You can collect the stream back into various types of collection:

List<Integer> testList = [1, 2, 3]

testList
   .stream()
   .map("do stuff here")
   .collect(Collectors.toList())

The above example collects the stream back as a list. You could replace toList() with toSet() if you wanted a set instead. You always need to use a Terminal operation like collect at the end of your stream.


Intermediate Operations

These methods transform the elements of stream, and return a stream themselves. Here is the full list of Intermediate Operations:

filter(Predicate<T>)
map(Function<T>)
flatmap(Function<T>)
sorted(Comparator<T>)
peek(Consumer<T>)
distinct()
limit(long n)
skip(long n)

I won’t cover all of them, but here is a few.

map

The .map() method allows you to apply a function to each element of the stream.

List<Integer> testList = [1, 2, 3]

testList
   .stream()
   .map(i -> i*i)
   .collect(toList())

System.out.println(testList)   // [1, 4, 9]

You can see I have changed Collectors.toList() to just toList(), you can do this by statically importing Collectors.


filter

The .filter() method uses a function to filter your collection, and returns a stream of matching elements.

List<String> testList = ["a1", "b1", "b2", "c1", "c2"]

testList
   .stream()
   .filter(s->s.startsWith("c"))
   .collect(toList())

System.out.println(testList)   // ["c1", "c2"]

Collecting after filtering is important, as it helps avoid null pointer issues. If no results are returned from the filter, collecting will create an empty collection.


sorted

.sorted() sorts your collection. It will sort it in ascending order by default.

List<Integer> testList = [2, 3, 1]

testList
   .stream()
   .sorted()
   .collect(toList())

System.out.println(testList)   // [1, 2, 3]


flatMap

flatMap works in a similar way to map, however where map only lets you transform one object directly into another object, flatMap lets you transform more complex collections. If your collection has nested collections, you can transform these with flatMap. After transforming, it flattens the collection into a single level.

Usually at the end of a flatMap you would stream() the results to either be collected or transformed again.

Here is an example from Geeks for Geeks:

// Creating a list of Prime Numbers 
List<Integer> PrimeNumbers = Arrays.asList(5, 7, 11,13); 
          
// Creating a list of Odd Numbers 
List<Integer> OddNumbers = Arrays.asList(1, 3, 5); 
          
// Creating a list of Even Numbers 
List<Integer> EvenNumbers = Arrays.asList(2, 4, 6, 8); 
  
List<List<Integer>> listOfListofInts = 
Arrays.asList(PrimeNumbers, OddNumbers, EvenNumbers); 
  
System.out.println("The Structure before flattening is : " + listOfListofInts); 
          
// Using flatMap for transformating and flattening. 
List<Integer> listofInts  = listOfListofInts.stream() 
                                .flatMap(list -> list.stream()) 
                                .collect(Collectors.toList()); 
  
System.out.println("The Structure after flattening is : " + listofInts)

Result:

The Structure before flattening is : [[5, 7, 11, 13], [1, 3, 5], [2, 4, 6, 8]]
The Structure after flattening is : [5, 7, 11, 13, 1, 3, 5, 2, 4, 6, 8]


Terminal Operations

These methods either return void or a non-stream result. collect is one of these, and here is the full list of Terminal Operations:

forEach
toArray
reduce
collect
min
max
count

// Short Circuiting Terminal Operations
anyMatch
allMatch
noneMatch
findFirst    
findAny

Short Circuiting Terminal Operations can finish before it has iterated over the whole collection.


forEach

The .forEach() method is used to iterate through every element in the collection. It works in a very similar way to map, however it is terminal so will return a non-stream result (or void).

List<Integer> testList = [2, 3, 1]

testList
   .stream()
   .forEach(i -> System.out.println(i))

Output:

2
3
1

Usually map is preferred to forEach, especially when using parrallelStream, as it performs much better.


anyMatch & allMatch

anyMatch and allMatch return a boolean depending on whether any element or all elements match the condition you supply.

Here is an example of anyMatch:

List<String> testList = ["a1", "b1", "b2", "c1", "c2"]

testList
   .stream()
   .anyMatch(s-> {
       System.out.println("anyMatch: " + s); // added to print out the current element being checked
       s.startsWith("b")
   })

Output:

anyMatch: a1
anyMatch: b1

As you can see above, it stops at b1, as this returns true, so it short circuits and returns true.
You may also notice that I included more than one function, by putting them inside {} and separating them with a ;.

Here is an example for allMatch:

List<Integer> testList = [2, 3, 1]

testList
   .stream()
   .allMatch(i -> i > 1)

// false


testList
   .stream()
   .allMatch(i -> i > 0)

// true

It works in the same way, but every element has to return true for the anyMatch to return true.

There is lots more I could go into for stream, as it is incredibly powerful and useful, but I will leave it there for now!


You can find the list of all of my Knowledge Sharing posts here.

Leave a comment