Java Stream API
Tags: java, stream, java8
Published: 2024-06-09 13:37:34

**Overview :** 
The Java Stream API facilitates processing sequences of elements, offering operations like filtering, mapping, and reducing. Streams can be used to perform operations in a declarative way, resembling SQL-like operations on data

**Key Concepts :** 
**Stream:** A sequence of elements supporting sequential and parallel aggregate operations

**Intermediate Operations:** Operations that return another stream and are lazy (e.g., filter, map)

**Terminal Operations:** Operations that produce a result or a side-effect and are not lazy (e.g., collect, forEach)

**Example Scenario :** 
Suppose we have a list of Person objects and we want to perform various operations on this list using the Stream API

```
public class Person {
    private String name;
    private int age;
    private String city;

    public Person(String name, int age, String city) {
        this.name = name;
        this.age = age;
        this.city = city;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getCity() {
        return city;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", city='" + city + "'}";
    }
}
```

Use Cases : 
1. Filtering
2. Mapping
3. Collecting
4. Reducing
5. FlatMapping
6. Sorting
7. Finding and Matching
8. Statistics

---

**Filtering :** Filtering allows you to select elements that match a given condition

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

public class Main {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30, "New York"),
            new Person("Bob", 20, "Los Angeles"),
            new Person("Charlie", 25, "New York"),
            new Person("David", 40, "Chicago")
        );

        // Filter people older than 25
        List<Person> filteredPeople = people.stream().filter(person -> person.getAge() > 25)                                       .collect(Collectors.toList());
        filteredPeople.forEach(System.out::println);
    }
}
```

---
**Mapping :**  Mapping transforms each element to another form using a function

```
public class Main {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30, "New York"),
            new Person("Bob", 20, "Los Angeles"),
            new Person("Charlie", 25, "New York"),
            new Person("David", 40, "Chicago")
        );
        // Get list of names
        List<String> names = people.stream()
                                   .map(Person::getName)
                                   .collect(Collectors.toList());
        names.forEach(System.out::println);
    }
}
```

---

**Collecting :** Collecting gathers the elements of a stream into a collection or other data structures

```
public class Main {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30, "New York"),
            new Person("Bob", 20, "Los Angeles"),
            new Person("Charlie", 25, "New York"),
            new Person("David", 40, "Chicago")
        );
        // Collect names into a set
        Set<String> uniqueCities = people.stream()
         .map(Person::getCity).collect(Collectors.toSet());
        uniqueCities.forEach(System.out::println);
    }
}
```

---
**Reducing :** Reducing performs a reduction on the elements of the stream using an associative accumulation function and returns an Optional
```
public class Main {
    public static void main(String[] args) {
         List<Person> people = Arrays.asList(
            new Person("Alice", 30, "New York"),
            new Person("Bob", 20, "Los Angeles"),
            new Person("Charlie", 25, "New York"),
            new Person("David", 40, "Chicago")
        );
        // Sum of ages
        int totalAge = people.stream()
                 .map(Person::getAge).reduce(0, Integer::sum);
        System.out.println("Total Age: " + totalAge);
    }
}
```

---
**FlatMapping :** FlatMapping flattens nested structures into a single stream.

```
public class Main {
    public static void main(String[] args) {
        List<List<String>> namesNested = Arrays.asList(
            Arrays.asList("John", "Doe"),
            Arrays.asList("Jane", "Smith"),
            Arrays.asList("Peter", "Parker")
        );

        List<String> namesFlat = namesNested.stream()
             .flatMap(List::stream).collect(Collectors.toList());
        namesFlat.forEach(System.out::println);
    }
}
```

---
**Sorting :** Sorting allows you to sort the elements of a stream

```
public class Main {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30, "New York"),
            new Person("Bob", 20, "Los Angeles"),
            new Person("Charlie", 25, "New York"),
            new Person("David", 40, "Chicago")
        );

        // Sort by age
        List<Person> sortedPeople = people.stream()
            .sorted(Comparator.comparing(Person::getAge))
            .collect(Collectors.toList());
        sortedPeople.forEach(System.out::println);
    }
}
```

---
**Finding and Matching :** 
Finding and matching operations check the elements of a stream to see if they match a given predicate
```
public class Main {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30, "New York"),
            new Person("Bob", 20, "Los Angeles"),
            new Person("Charlie", 25, "New York"),
            new Person("David", 40, "Chicago")
        );

        // Find any person living in New York
        Optional<Person> personInNY = people.stream()
               .filter(person -> "NewYork".equals(person.getCity())).findAny();

        personInNY.ifPresent(System.out::println);

        // Check if all people are older than 18
        boolean allAdults = people.stream()
          .allMatch(person -> person.getAge() > 18);

        System.out.println("All adults: " + allAdults);
    }
}

```

---
**Statistics :** The Stream API can also be used to perform various statistical operations like counting, averaging, etc.
```
public class Main {
    public static void main(String[] args) {
       List<Person> people = Arrays.asList(
            new Person("Alice", 30, "New York"),
            new Person("Bob", 20, "Los Angeles"),
            new Person("Charlie", 25, "New York"),
            new Person("David", 40, "Chicago")
        );

        // Count number of people
        long count = people.stream().count();
        System.out.println("Number of people: " + count);

        // Calculate average age
        Double averageAge = people.stream()
        .collect(Collectors.averagingInt(Person::getAge));

        System.out.println("Average Age: " + averageAge);
    }
}

```
**Practical Example :** 
Here's a comprehensive example that uses several of the features mentioned above:

```
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30, "New York"),
            new Person("Bob", 20, "Los Angeles"),
            new Person("Charlie", 25, "New York"),
            new Person("David", 40, "Chicago")
        );

        // Filter, map, sort, and collect
        List<String> names = people.stream()
                                   .filter(person -> person.getAge() > 20)
                                   .map(Person::getName)
                                   .sorted()
                                   .collect(Collectors.toList());

        names.forEach(System.out::println);

        // Find the oldest person
        Optional<Person> oldestPerson = people.stream()
                                              .max(Comparator.comparing(Person::getAge));

        oldestPerson.ifPresent(person -> System.out.println("Oldest Person: " + person));

        // Group by city
        Map<String, List<Person>> peopleByCity = people.stream()
                                                       .collect(Collectors.groupingBy(Person::getCity));

        peopleByCity.forEach((city, peopleInCity) -> {
            System.out.println("People in " + city + ": " + peopleInCity);
        });

        // Calculate total and average age
        IntSummaryStatistics ageStatistics = people.stream()
                                                   .collect(Collectors.summarizingInt(Person::getAge));

        System.out.println("Total Age: " + ageStatistics.getSum());
        System.out.println("Average Age: " + ageStatistics.getAverage());
    }
}

```
**Summary :**
The Java Stream API is a powerful tool for working with collections and data. It allows for:

**Filtering:** Select elements based on a condition

**Mapping:** Transform elements

**Collecting:** Gather elements into collections or other data structures

**Reducing:** Combine elements into a single result.

**FlatMapping:** Flatten nested structures.

**Sorting:** Order elements.

**Finding and Matching:** Check elements against a condition.

**Statistics:** Perform statistical operations.

Understanding these features will help you write cleaner, more concise, and more readable code.

**Happy Coding...**