# Java Streams

## Syntax

parts of a stream: <span style="color: rgb(191, 237, 210);">input</span>, <span style="color: rgb(236, 202, 250);">intermediate operation(s)</span>, <span style="color: rgb(251, 238, 184);">terminal operation</span>

<span style="color: rgb(191, 237, 210);">IntStream.range(0, 10)</span><span style="color: rgb(236, 202, 250);">.filter(x -> x % 2 == 0).peek(System.out::println)</span><span style="color: rgb(251, 238, 184);">.reduce(0, (x, y) -> x + y)</span>

## Input

Collection - List, Set, etc.:

```java
import java.util.List;
import java.util.Arrays;

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().forEach(System.out::println);
```

Collection - Map:

```java
import java.util.Map;
import java.util.HashMap;
import java.util.Set;

Map<String, Integer> studentScores = new HashMap<>();
studentScores.put("Alice", 85);
studentScores.put("Bob", 90);
studentScores.put("Charlie", 75);

Set<Map.Entry<String, Integer>> entries = studentScores.entrySet();
entries.stream().forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));
```

Array:

```java
import java.util.Arrays;

int[] numbers = {1, 2, 3, 4, 5};
Arrays.stream(numbers).forEach(System.out::println);
```

**Stream.empty()** - used to avoid returning null:

```java
import java.util.stream.Stream;
import java.util.List;

// source: Baeldung: https://www.baeldung.com/java-8-streams
public Stream<String> streamOf(List<String> list) {
    return list == null || list.isEmpty() ? Stream.empty() : list.stream();
}
```

**Stream.generate()**:

```java
import java.util.stream.Stream;
import java.util.Random;

// Generate 10 random even numbers between 0 (inclusive) and 100 (exclusive)
Stream<Integer> evenNumbers =
    Stream.generate(() -> new Random().nextInt(100)).filter(n -> n % 2 == 0);
evenNumbers.limit(10).forEach(System.out::println);
```

**Stream.iterate(seed, hasNext, next)** - hasNext param only from Java 9:

```java
import java.util.stream.Stream;

// Print all powers of 2 ≤ 20 – starts from 1, multiplies by 2 at each iteration
Stream<Integer> stream = Stream.iterate(1, i -> i <= 20, i -> i * 2);
stream.forEach(System.out::println);
```

**Random.doubles/ints/longs()** - 0-3 parameters; 1 parameter: stream size; 2 parameters: lower bound (inclusive), upper bound (exclusive); 3 parameters: stream size, upper and lower bound:

```java
import java.util.Random;
import java.util.stream.IntStream;

Random random = new Random();
IntStream randomNumbers = random.ints(10, 1, 100);
randomNumbers.forEach(System.out::println);
```

**Stream.of()**:

```java
import java.util.stream.Stream;

Stream<String> linuxDistros =
    Stream.of("Debian", "Fedora", "Arch", "Ubuntu", "Manjaro", "Mint", "Zorin");
linuxDistros.forEach(System.out::println);
```

**IntStream, LongStream, DoubleStream** **of()** method:

```java
import java.util.stream.IntStream;

IntStream integers = IntStream.of(1, 2, 3, 4, 5);
integers.forEach(System.out::println);
```

**Stream.builder()**:

```java
import java.util.stream.Stream;

Stream<String> linuxDistros =
    Stream.<String>builder().add("Debian").add("Arch").add("Fedora").build();
linuxDistros.forEach(System.out::println);
```

**IntStream, LongStream, DoubleStream** **range(startInclusive, endExclusive)** and **rangeClosed(startInclusive, endInclusive)** methods:

```java
import java.util.stream.IntStream;

IntStream intRange = IntStream.range(1, 5);
intRange.forEach(System.out::println); // 1, 2, 3, 4
```

**Files.lines()**:

```java
import java.util.stream.Stream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;

try (Stream<String> lines = Files.lines(Paths.get("data.txt"))) {
    lines.forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}
```

**Optional**:

```java
import java.util.stream.Stream;
import java.util.Optional;

Stream<Integer> opt = Optional.of(42).stream();
opt.forEach(System.out::print);
```

**Pattern.compile().splitAsStream()** - split a string with a regex:

```java
import java.util.stream.Stream;
import java.util.regex.Pattern;

// Split "hello world" along one or more whitespace characters
Stream<String> words = Pattern.compile("\\s+").splitAsStream("hello world");
words.forEach(System.out::println);
```

## Intermediate operations

**filter()** - return elements of a stream which match a condition:

```java
import java.util.stream.Stream;
import java.util.Arrays;

Stream<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
numbers.filter(n -> n % 2 == 0).forEach(System.out::println); // 2, 4, 6
```

**map()** - call a method on each element of the stream:

```java
import java.util.stream.Stream;
import java.util.Arrays;

Stream<String> names = Arrays.asList("Alice", "Bob", "Charlie").stream();
names.map(String::toUpperCase).forEach(System.out::println);
// "ALICE", "BOB", "CHARLIE"
```

**mapToInt(), mapToLong(), mapToDouble()**:

```java
import java.util.stream.Stream;
import java.util.Arrays;

Stream<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
numbers.mapToInt(n -> n).forEach(System.out::println); // 1, 2, 3, 4, 5, 6
```

**flatMap()** - calls a method on all elements of a stream, returns the result as a single stream:

```java
import java.util.stream.Stream;
import java.util.Arrays;

Stream<String> listOfStrings = Arrays.asList("Hello", "World").stream();
listOfStrings.flatMap(s -> Stream.of(s.split(""))).forEach(System.out::println);
// "H", "e", "l", "l", "o", "W", "o", "r", "l", "d"
```

**limit()** - return only a specified number of elements:

```java
import java.util.stream.Stream;

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6);
numbers.limit(3).forEach(System.out::println); // 1, 2, 3
```

**distinct()** - no duplicates:

```java
import java.util.stream.Stream;
import java.util.Arrays;

Stream<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5).stream();
numbers.distinct().forEach(System.out::println); // 1, 2, 3, 4, 5
```

**skip()** - exclude an element, note that elements are counted from 1, not 0!:

```java
import java.util.stream.Stream;

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6);
numbers.skip(1).forEach(System.out::println); // 2, 3, 4, 5, 6
```

**sorted()**:

```java
import java.util.stream.Stream;

Stream<String> numbers = Stream.of("World", "Hello");
numbers.sorted().forEach(System.out::println); // "Hello", "World"
```

**peek()** - used mainly for debugging to print out intermediary values:

```java
import java.util.stream.Stream;

Stream.of("Mint", "Zorin", "Bazzite", "Nobara", "LMDE")
      .filter(e -> e.length() > 5)
      .peek(e -> System.out.println("Filtered string: " + e))
      .map(String::toUpperCase)
      .peek(e -> System.out.println("Mapped string: " + e))
      .map(String::length)
      .forEach(e -> System.out.println("Length of string: " + e));
/*
Filtered string: Bazzite
Mapped string: BAZZITE
Length of string: 7
Filtered string: Nobara
Mapped string: NOBARA
Length of string: 6
*/
```

**takeWhile(), dropWhile()** - only supported from Java 9 - take/drop elements until an element matches the provided condition:

```java
import java.util.List;
import java.util.Arrays;

List<Integer> numbers = Arrays.asList(1, 30, 2, 40, 21, 14);
numbers.stream().takeWhile(s -> s < 40).forEach(System.out::println);
// 1, 30, 2
numbers.stream().dropWhile(s -> s < 40).forEach(System.out::println);
// 40, 21, 14
```

**boxed()** - convert a primitive stream to an object stream (e.g. stream of ints to stream of Integers):

```java
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Collectors;

List<Integer> numbers = IntStream.of(1,2,3).boxed().collect(Collectors.toList());
```

## Terminal operations

**forEach()**:

```java
import java.util.stream.Stream;

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
numbers.forEach(System.out::println);
```

**count()**:

```java
import java.util.stream.Stream;

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
long count = numbers.count(); // 5
```

**min(), max()**:

```java
import java.util.stream.Stream;
import java.util.Optional;

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
Optional<Integer> min = numbers.min(Integer::compare); // Optional[1]
Integer minValue = min.get(); // 1
```

**sum()** - only works on primitive streams (IntStream, DoubleStream, LongStream)!:

```java
import java.util.stream.IntStream;

IntStream numbers = IntStream.of(1, 2, 3);
int total = numbers.sum(); // 6
```

**average()** - only works on primitive streams (IntStream, DoubleStream, LongStream)!:

```java
import java.util.stream.IntStream;
import java.util.OptionalDouble;

IntStream numbers = IntStream.of(1,2,3);
OptionalDouble avg = numbers.average(); // OptionalDouble[2.0]
double avgValue = avg.getAsDouble(); // 2.0
```

**collect()** - only works on object streams (e.g. Stream&lt;Integer&gt;, not IntStream):

```java
import java.util.stream.Stream;
import java.util.List;
import java.util.stream.Collectors;

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
List<Integer> collect = numbers.collect(Collectors.toList());
```

See Collectors section of this cheatsheet below for some of the common Collectors.

**toArray()** - only works on object streams (e.g. Stream&lt;Integer&gt;, not IntStream), array also needs to be object array (e.g. Integer\[\], not int\[\]):

```java
import java.util.Arrays;
import java.util.stream.Stream;

Stream<Integer> listStream = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
Integer[] numArray = listStream.toArray(Integer[]::new);
```

**reduce()** - parameters: 1. base value (optional), 2. function with element and next element as parameters, at first iteration, base value is used (if it's provided):

```java
import java.util.stream.Stream;
import java.util.Optional;

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Integer sum = numbers.stream().reduce(0, (a, b) -> a + b); // 15
Optional<Integer> sum2 = numbers.stream().reduce((a, b) -> a + b); // Optional[15]
Integer sum2Value = sum2.get(); // 15
```

**allMatch(), anyMatch(), noneMatch()** - check if all, any or no elements match a condition:

```java
import java.util.List;
import java.util.Arrays;

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); // false
boolean containsEven = numbers.stream().anyMatch(n -> n % 2 == 0); // true
boolean noEven = numbers.stream().noneMatch(n -> n % 2 == 0); // false
```

**findFirst()**:

```java
import java.util.stream.Stream;
import java.util.Optional;

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
Optional<Integer> first = numbers.findFirst(); // Optional[1]
Integer firstValue = first.get(); // 1
```

### Collectors

**toList(), toSet()**:

```java
import java.util.stream.Stream;
import java.util.List;
import java.util.stream.Collectors;

Stream<String> stream = Stream.of("Debian", "Fedora", "Arch", "Ubuntu", "Mint");
List<String> linuxDistros = stream.collect(Collectors.toList());
```

**joining()** - 0, 1, or 3 parameters; 1: delimiter; 3: delimiter, prefix, suffix:

```java
import java.util.stream.Stream;
import java.util.stream.Collectors;

List<String> list = List.of("Debian", "Fedora", "Arch", "Ubuntu", "Mint");
String linuxDistros1 = list.stream().collect(Collectors.joining());
// DebianFedoraArchUbuntuMint
String linuxDistros2 = list.stream().collect(Collectors.joining(", "));
// Debian, Fedora, Arch, Ubuntu, Mint
String linuxDistros3 = list.stream().collect(Collectors.joining(", ", "[", "]"));
// [Debian, Fedora, Arch, Ubuntu, Mint]

```

**summarizingInt/Double/Long()**:

```java
import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.util.IntSummaryStatistics;

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
IntSummaryStatistics stats = stream.collect(Collectors.summarizingInt(i -> i));
// IntSummaryStatistics{count=6, sum=21, min=1, average=3.500000, max=6}
```

**averagingInt/Double/Long(), summingInt/Double/Long()**:

```java
import java.util.List;
import java.util.stream.Collectors;

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
double avg = numbers.stream().collect(Collectors.averagingDouble(Double::valueOf));
// 3.5
int sum = numbers.stream().collect(Collectors.summingInt(Integer::intValue));
// 21
```

**counting()**:

```java
import java.util.stream.Stream;
import java.util.stream.Collectors;

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6);
long counter = numbers.collect(Collectors.counting()); // 6
```

**groupingBy()**:

```java
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.util.Map;

List<String> linuxDistros = List.of("Debian", "Fedora", "Arch", "Ubuntu", "Mint");
Map<Integer, List<String>> byLen =
    linuxDistros.stream().collect(Collectors.groupingBy(String::length));
// {4=[Arch, Mint], 6=[Debian, Fedora, Ubuntu]}
Map<Integer, Long> byLen2 = linuxDistros.stream()
    .collect(Collectors.groupingBy(String::length, Collectors.counting()));
// {4=2, 6=3}
```

**partitioningBy()** - similar to groupingBy(), but creates a true and a false group:

```java
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.util.Map;

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
Map<Boolean, List<Integer>> isEven =
    stream.collect(Collectors.partitioningBy(i -> i % 2 == 0));
// {false=[1, 3, 5], true=[2, 4, 6]}
```

**reducing()**:

```java
import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.util.Optional;

Stream<String> stream = Stream.of("Arch", "Slackware", "Debian", "Zorin", "Pop!_OS");
Optional<String> longest =
    stream.collect(Collectors.reducing((a,b) -> a.length() >= b.length() ? a : b));
String longestValue = longest.get(); // Slackware
```

For all methods of Collector, see the [official documentation](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html).

## Parallel streams

Create parallel stream, using **parallelStream()** method:

```java
import java.util.List;
import java.util.stream.Stream;

List<String> linuxDistros = List.of("Arch", "Slackware", "Debian", "Zorin", "Pop!_OS");
Stream<String> stream = linuxDistros.parallelStream();
```

Convert an existing stream to a parallel stream, using **parallel()** method:

```java
import java.util.stream.Stream;

Stream<String> linuxDistros =
    Stream.of("Arch", "Slackware", "Debian", "Zorin", "Pop!_OS");
Stream<String> linuxDistrosParallel = linuxDistros.parallel();
```

Convert a parallel stream back to sequential, using **sequential()** method:

```java
import java.util.stream.Stream;

Stream<String> linuxDistrosParallel =
    Stream.of("Arch", "Slackware", "Debian", "Zorin", "Pop!_OS").parallel();
Stream<String> linuxDistros = linuxDistrosParallel.sequential();
```

### Terminal operations for parallel streams

**findAny()** - return any element:

```java
import java.util.stream.Stream;
import java.util.Optional;

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5).parallel();
Optional<Integer> number = numbers.findAny();
Integer numberValue = first.get(); // 3
```

**forEachOrdered()** - keeps the order of elements (forEach() doesn't keep it in case of a parallel stream):

```java
import java.util.stream.Stream;

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5).parallel();
numbers.forEachOrdered(System.out::println);
```

**collect(Collectors.groupingByConcurrent())** - more efficient, parallel stream variant of groupingBy(), doesn't keep order of elements (unlike groupingBy()):

```java
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.util.Map;

List<String> linuxDistros = List.of("Debian", "Fedora", "Arch", "Ubuntu", "Mint");
Map<Integer, List<String>> byLen = linuxDistros.parallelStream()
    .collect(Collectors.groupingByConcurrent(String::length));
// {4=[Arch, Mint], 6=[Ubuntu, Fedora, Debian]}
Map<Integer, Long> byLen2 = linuxDistros.parallelStream()
    .collect(Collectors.groupingByConcurrent(String::length, Collectors.counting()));
// {4=2, 6=3}
```
