# More on Streams

The streams were introduced in my [previous article](https://blog.programwithjr.com/introduction-to-streams). This is the second article in the series.

## Comparison-based stream operations

We will explain several common methods, some of them were mentioned in the previous article.

### sorted

It sorts the elements of the stream based on the comparator provided.

```java
List<Employee> listOfEmployeesSortedByName = listOfEmployees
    .stream()
    .sorted((e1, e2) -> e1.getName().compareTo(e2.getName()))
    .collect(Collectors.toList());
```

### min and max

```java
var youngestEmployee = listOfEmployees
    .stream()
    .min((e1, e2) -> e1.getAge() - e2.getAge())
    .orElseThrow(NoSuchElementException::new);
```

Defining the comparison can be avoided by using `Comparator.comparing()`:

```java
var oldestEmployee = listOfEmployees
    .stream()
    .max(Comparator.comparing(Employee::getAge))
    .orElseThrow(NoSuchElementException::new);
```

### distinct

It does not take any arguments and returns a stream with distinct elements, eliminating duplicates. It uses the `equals()` method to decide whether two elements are equal. A common pattern is to use Sets to remove duplicates. `distinct()` achieves the same result in a more elegant way.

```java
List<Integer> listOfDistinctNumbers = 
Arrays.asList(1, 2, 3, 4, 5, 1, 2, 6, 7, 3)
    .stream()
    .distinct()
    .collect(Collectors.toList());
```

### allMatch, anyMatch and noneMatch

These operations are used to check if all, any or none of the elements in the stream match a certain condition. They take a predicate and return a boolean. The processing is stopped as soon as the answer is determined. This technique, also used by the Java logical operators, is called **short-circuiting**:

```java
boolean allEven = listOfNumbers.stream()
                     .allMatch(n -> n % 2 == 0);
boolean atLeastOneEven = listOfNumbers.stream()
                     .anyMatch(n -> n % 2 == 0);
boolean noneEven = listOfNumbers.stream()
                     .noneMatch(n -> n % 2 == 0);
```

## Stream Specializations

We worked with `Stream<T>` so far. But there are other stream types, which are used in specific situations. The most used ones are:

* `IntStream`
    
* `LongStream`
    
* `DoubleStream`
    

These specialized streams are used to perform operations on primitive types. They extend `BaseStream<T>` type, not `Stream<T>`.

```java
IntStream extends BaseStream<Integer>
LongStream extends BaseStream<Long>
DoubleStream extends BaseStream<Double>
Stream<T> extends BaseStream<T>
```

To create a specialized stream, we use the static factory method `of()` of the corresponding type.

```java
IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
LongStream longStream = LongStream.of(1L, 2L, 3L, 4L, 5L);
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0, 4.0, 5.0);
```

## Map variants: mapToInt, mapToLong and MapToDouble

The `map()` operation, as explained in the previous article, returns a Stream type. But when dealing with numerical data to achieve statistics results, returning a number is more convenient.

A default value is not required for the `sum()` operation on an **IntStream**, because sum() returns 0 for an empty stream. Therefore, sum() is a **terminal** operation.

```java
List<Integer> listOfNumbers = Arrays.asList(1, 2, 3, 4, 5);
```

```java
int sum = listOfNumbers
    .stream()
    .mapToInt(n -> n)
    .sum();
```

Unlike `sum()`, the `average()` method returns an `OptionalDouble` to account for empty streams. Attempting to assign this directly to a double without handling the empty case will result in a compilation error. Other methods with a similar behavior -they do not return a primitive value- are `min()` and `max()`.

```java
OptionalInt optionalMinimum = listOfNumbers
    .stream()
    .mapToInt(Integer::intValue)
    .min();
```

There are three possible solutions:

* Use the `orElse()` method to provide a default value.
    
* Use the `orElseThrow()` method to throw an explicit error.
    
* Use `getAsDouble()` if we assume the list will never be empty.
    

Notice the two first methods are part of the `Optional<T>` API.

```java
int minimum = listOfNumbers
    .stream()
    .mapToInt(Integer::intValue)  // Convert to IntStream
    .min()
    .orElse(0);
```

```java
double average = listOfNumbers
    .stream()
    .mapToInt(Integer::intValue)  // Convert to IntStream
    .average()                    // Convert to OptionalDouble
    .orElseThrow(() -> new IllegalArgumentException("List is empty"));
```

```java
double average = listOfNumbers
    .stream()
    .mapToDouble(Integer::intValue)  
    .average()     // Convert to OptionalDouble 
    .getAsDouble();
```

## Statistics

The `summaryStatistics()` method provides combined statistics like count, sum, min, average, and max.

```java
IntSummaryStatistics stats = listOfNumbers
    .stream()
    .mapToInt(Integer::intValue)
    .summaryStatistics();

System.out.println("Count: " + stats.getCount());
System.out.println("Sum: " + stats.getSum());
System.out.println("Min: " + stats.getMin());
System.out.println("Max: " + stats.getMax());
System.out.println("Average: " + stats.getAverage());
```

## File operations

Streams can also be used in file operations. For example, to read a file line by line and print those lines:

```java
Stream<String> lines = Files.lines(Paths.get("file.txt"));
lines.forEach(System.out::println);
```

## Improvements in Java 9

We will explore new methods like `takeWhile` and `dropWhile` that are used to perform operations on infinite streams.

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

List<Integer> listOfNaturalNumbers = infiniteStream
    .takeWhile(n -> n <= 10)
    .map(x -> x * x)
    .forEach(System.out::println);  
    // Prints 1, 4, 9, 16, 25, 36, 49, 64, 81, 100
```

In that example, using `filter()` instead of `takeWhile()` yields the same result. But the two methods opperates differently: `takeWhile()` stops processing as soon as the predicate is false, whereas `filter` evaluates the entire stream.

`dropWhile()` is the opposite of `takeWhile()`. Instead of taking elements while a condition is true, `dropWhile` skips elements while the condition is true and starts returning elements when the condition becomes false. 

These two methods already existed in the **Scala** and **Kotlin** APIs, but not in Java.

