DEV Community

Abhishek Singh
Abhishek Singh

Posted on

6 1

Java Stream API

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 + "'}";
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}

Enter fullscreen mode Exit fullscreen mode

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);
    }
}

Enter fullscreen mode Exit fullscreen mode

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());
    }
}

Enter fullscreen mode Exit fullscreen mode

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...

Top comments (0)

Great read:

Is it Time to go Back to the Monolith?

History repeats itself. Everything old is new again and I’ve been around long enough to see ideas discarded, rediscovered and return triumphantly to overtake the fad. In recent years SQL has made a tremendous comeback from the dead. We love relational databases all over again. I think the Monolith will have its space odyssey moment again. Microservices and serverless are trends pushed by the cloud vendors, designed to sell us more cloud computing resources.

Microservices make very little sense financially for most use cases. Yes, they can ramp down. But when they scale up, they pay the costs in dividends. The increased observability costs alone line the pockets of the “big cloud” vendors.