# Introduction to Streams

## Introduction to Streams

Streams are a sequence of elements that can be processed in a lazy way.

The Stream API is a feature introduced in version 8 of the Java platform that provides an efficient way to process collections. In Java 9, several improvements were made.

The Stream API is based on the concept of a pipeline of operations that can be applied to a stream of elements. This pipeline can be divided in three parts:

* Stream creation
    
* Intermediate operations
    
* Terminal operations
    

A stream pipeline consists of a stream source, followed by zero or more intermediate operations, and finally a terminal operation. Notice that the terminal operation is the only one that returns a value.

Example of a stream pipeline without any intermediate operation:

```java
streamOfEmployees
   .foreach(e -> System.out.println(Employee::getName));
```

That was the formal definition of a stream. In practice, it can replace many loops, resulting in a much more readable and less error-prone code.

```java
public List<Integer> getEvenNumbers(List<Integer> listOfNumbers) {
    List<Integer> listOfEvenNumbers = new ArrayList<>();
    for (Integer number : listOfNumbers) {
        if (number % 2 == 0) {
            listOfEvenNumbers.add(number);
        }
    }
    return listOfEvenNumbers;
}
```

Using streams:

```java
public List<Integer> getEvenNumbers(List<Integer> listOfNumbers) {
    return listOfNumbers
        .stream()
        .filter(n -> n % 2 == 0)
        .collect(Collectors.toList());
}
```

This is called a **declarative way** of writing code, because we are writing what result we want to achieve, without worrying about how to do it.

## Stream creation

A stream can be created from an array or a collection.

```java
private Employee[] employees = {
    new Employee("Jason", 30),
    new Employee("Maria", 32),
};

var streamOfEmployees = Stream.of(employees)
```

```java
private List<Employee> employeesList = Arrays.asList(employees);

var streamOfEmployees = employeesList.stream()
```

We can use factory methods to create streams.

```java
var streamOfIntegers = Stream.of(1, 2, 3, 4, 5)
```

```java
var streamOfEmployees = Stream.of(
    new Employee("Jason", 30),
    new Employee("Maria", 32)
)
```

or using `Stream.builder()`

```java
Stream.Builder<Employee> streamBuilder = Stream.builder();
streamBuilder.accept(new Employee("Jason", 30));
streamBuilder.accept(new Employee("Maria", 32));
var streamOfEmployees = streamBuilder.build();
```

## Stream operations

### forEach

It is used to perform an action by calling the supplied transformation (lambda expression) on each element of the stream.

```java
streamOfEmployees.forEach(e -> e.getName());
```

**forEach() is a terminal operation**.

### map

It produces a new stream whose elements are the results of applying the given function to the elements of an existing stream.

```java
var streamOfNames = streamOfEmployees
    .map(Employee::getName)      // or .map(e -> e.getName())
    .collect(Collectors.toList());
```

Notices map is an intermediate operation, therefore we have to use a Collector after the transformation.

### filter

It is similar to map but it filters the elements of the stream.

```java
var streamOfNames = streamOfEmployeesInTheirThirties
    .filter(e -> e != null)
    .filter(e -> e.getAge() >= 30)
    .collect(Collectors.toList());
```

### findFirst

It returns an Optional containing the first element of the stream.

```java
var employee = streamOfEmployees
    .map(employeeRepository::findById)
    .findFirst()
    .orElse(null);
```

**findFirst() is a terminal operation**.

### flatMap

It is similar to map but it flattens the elements of the stream. An example is converting a list of lists to a list of elements.

```java
var ListOfNames = listOfEmployees
    .stream()
    // .map(Employee::getName)
    .flatMap(Collection::stream)
    .collect(Collectors.toList());
```

### Other stream operations

The following ones are only mentioned because they are very common. Their names are self explanatory:

* `distinct()`
    
* `sorted()`
    
* `limit()`
    
* `skip()`
    
* `peek()`
    
* `anyMatch()`
    
* `allMatch()`
    
* `noneMatch()`
    
* `min()`
    
* `max()`
    

## Terminal operations

### count

It returns the number of elements in the stream.

```java
var numberOfEmployees = streamOfEmployees.count();
```

### reduce

It reduces the stream to a single value.

```java
var sumOfAges = streamOfEmployees
    .map(Employee::getAge)
    .reduce(0, Integer::sum);
```

Unlike other languages, `reduce` must receive a starting value.

A **reduction operation**, also called a fold, takes a sequence of elements and combines them into a single value by repeated applications of a combining operation. Other reduction operations are **findFirst()**, **min()** and **max()**.

```java
var listOfNumber = List.of(1, 2, 3, 4, 5);

var sumOfNumbers = listOfNumber
    .stream()
    .reduce(0, Integer::sum);
```

or

```java
var sumOfNumbers = listOfNumber
    .mapToInt(Integer::intValue)
    .sum();
```

### collect

It is used to collect the elements of the stream into a container.

The strategy for this operation is provided via the `Collector` interface. There are several implementations of this interface, but the most common ones are the ones related to the three collection types:

* `Collectors.toList()`
    
* `Collectors.toSet()`
    
* `Collectors.toMap()`
    

### toArray

It returns an array containing the elements of the stream.

```java
Employee[] ArrayOfEmployees = streamOfEmployees
    .toArray(Employee[]::new);
```

## Lazy evaluation

In the beginning of the article we mentioned that the Stream API is lazy. This means that the stream pipeline is not executed until the terminal operation is called.

Why should we care? Let's see an example.

```java
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);

List<Integer> listOfNaturalNumbers = infiniteStream
    .skip(3)
    .limit(10)
    .collect(Collectors.toList());
```

```java
System.out.println(listOfNaturalNumbers);
```

The output is `[4, 5, 6, 7, 8, 9, 10, 11, 12, 13]`.

The stream pipeline above will be executed only when the terminal operation is called. So **all intermediate operations are lazy**. This technique allows computations on finite streams to be completed in finite time. In other words, the evaluation of the functions is separated from the description of the transformation. This is the definition of lazy evaluation.

One of the most important characteristics of the streams is that they allow for **significant optimizations through lazy evaluations**.

## Parallel Streams

Parallel streams are streams that are executed in parallel.

```java
Stream<Integer> parallelStream = Stream.of(1, 2, 3, 4, 5)
    .parallel()
    .map(n -> n * n);
```

As is the case when writing multi-threaded code, we need to be aware of a few things while using parallel streams:

* We need to ensure that the code is thread-safe. Take special care if the operations performed in parallel modify shared data.
    
* We should not use parallel streams if the order in which operations are performed or the order returned in the output stream matters. For example, operations like findFirst() may generate different results in the case of parallel streams.
    
* Also, we should ensure that it’s worth making the code execute in parallel. Understanding the performance characteristics of the operation in particular, but also of the system as a whole, is naturally very important.
    

## Infinite Streams

Also called unbounded streams, infinite streams are used to perform operations while the elements are still being generated. However, when using *map*, all the elements are already populated.

There are two ways to generate infinite streams:

### generate

```java
// Print ten random numbers
Stream.generate(Math::random)
    .limit(10)
    .forEach(System.out::println);
```

### iterate

It takes an initial value, called the seed and a function that generates the next element using the previous value. *iterate()* is statefull, therefore it may not be useful in parallel streams:

```java
Stream<Integer> evenNumbersStream = Stream.iterate(2, i -> i * 2)
```

```java
public getFirstEvenNumbers(int size) {
    return Stream.iterate(2, i -> i * 2) // evenNumbersStream 
        .limit(size)
        .collect(Collectors.toList());
}
```

*limit()* is the terminating condition.

## Summary

The motivations behind Java streams were explained and several examples were presented. Several of the most common transformation were explained or mentioned.

The lazy evaluation technique was explained, which is used by many methods of the Stream API and allows to perform operations on infinite streams in a finite time. Parallel streams were created, which are streams that are executed in parallel.

The stream API is a powerful tool that can be used to perform operations on collections in a declarative way. It is remarkable change for any Java programmer.
