Top 10 Java 8 Stream Practice Problem : Stream API

Top 10 Java 8 Stream Practice Problem : Stream API

Overview

Java 8 introduced a game-changing feature, the Stream API. It provides the feature to process collections of data in a declarative and efficient way, making your code cleaner, more readable, and often faster. If you’re looking to level up your Java skills then mastering the Stream API in java is a must. Because Stream api is most frequently asked interview question in java

This guide will help you practice and understand common problems using the Java Stream API. We’ll go with different types of examples step by step, with simple explanations and easy code samples. By the end, you’ll be mastering using Java 8 streams for your own projects.

java 8 stream practice problems

What is the Java 8 Stream API?

Before solving the java 8 stream api practice problems, let’s understand what the Stream API is all about. Java stream api supports many groups of operations. Unlike collections, streams don’t store data; instead, they process data from a source (like a collection, array, or I/O channel) in a pipeline of operations.

The core concepts of the Stream API revolve around:

  • Source Where the stream originates (e.g., a List, Set, Map (through its views), or an array).
  • Intermediate Operations: These operations transform or filter the stream. They are Working in a lazy way, that means they are not executed until a terminal operation is invoked. Examples include filter(), map(), sorted(), distinct().
  • Terminal Operations:: These are the final operations in a stream that either give you a result or perform an action, like printing or saving data. They consume the stream and mark its end. Examples like forEach(), collect(), reduce(), count(), anyMatch(), allMatch().

Top 10 Java 8 Stream API Practical Problems

Let’s practice the Stream API with hands-on problems to truly understand it. By doing these practice problems, you will master the Stream API in Java.

1. Filtering a List of Integers

Example Let’s suppose we have a list of integers and we need to filter out only the even numbers of that list.


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

public class StreamExample {

    public static void main(String[] args) {

        List<Integer> integerList = Arrays.asList(11, 12, 13, 14, 15, 16, 17, 18, 19, 20);

        List<Integer> evenNumbers = integerList.stream()
            .filter(num -> num % 2 == 0)
            .collect(Collectors.toList());

        System.out.println(evenNumbers); // Output: [12, 14, 16, 18, 20]
    }
}

Explanation:

  • In starting we have list of numbers: This list is the input for the stream operation.
  • nums.stream(): Creates a stream from the list, enabling functional-style operations.
  • .filter(nums -> nums % 2 == 0): An intermediate operation that retains only even numbers by applying the given lambda expression.
  • .collect(Collectors.toList()): A terminal operation that collects the filtered stream elements into a new List.

2. Filtering a List of Integers

Example Let’s suppose we have a list of integers and we need to filter out only the even numbers of that list.


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

public class StreamMapExample {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("apple", "banana", "cherry");

        List<String> upperCaseWords = words.stream()
            .map(String::toUpperCase)
            .collect(Collectors.toList());

        System.out.println(upperCaseWords); // Output: [APPLE, BANANA, CHERRY]
    }
}

Explanation:

  • words.stream(): Creates a stream of strings from the list, enabling stream operations.
  • .map(String::toUpperCase): An intermediate operation that transforms each string in the stream to uppercase using a method reference.
  • .collect(Collectors.toList()): A terminal operation that collects the transformed strings into a new List.

3. Finding the First Element in a List

Example For Example we have list of string and we want first element in that list of strings.


import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class FindFirstExample {

    public static void main(String[] args) {

        List<String> colors = Arrays.asList("red", "green", "blue");

        Optional<String> firstColor = colors.stream()
                                             .findFirst();

        firstColor.ifPresent(color ->
            System.out.println("The first color is: " + color)
        ); // Output: The first color is: red
    }
}

Explanation:

  • .findFirst(): A terminal operation that returns the first element from the stream, wrapped in an Optional. It handles cases where the stream might be empty.
  • ifPresent(): A method of the Optional class that executes the provided lambda expression only if a value is present.

4. Calculating the Sum of Numbers in a List

Example We want to calculate the sum of all number in list


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

public class ReduceExample {

    public static void main(String[] args) {

        List<Integer> values = Arrays.asList(10, 20, 30, 40, 50);

        int numberSum = values.stream()
                              .reduce(0, Integer::sum);

        System.out.println("This is sum of all numbers in list : " + numberSum); 
        // Output: This is sum of all numbers in list : 150
    }
}

Explanation:

  • .reduce(0, Integer::sum): A terminal operation that combines all elements of the stream into a single result.
  • 0: The initial value (also known as the identity value) for the reduction operation.
  • Integer::sum: A method reference to the sum() method of the Integer class, which performs addition of elements.

5. Counting Elements Based on a Condition

Example we need to Count the number of strings in the list which length is greater then 2


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

public class FruitCount {

    public static void main(String[] args) {

        List<String> fruits = Arrays.asList("apple", "banana", "kiwi", "orange", "grape");

        long count = fruits.stream()
                              .filter(fruit -> fruit.length() > 4)
                              .count();

        System.out.println("Count of fruits which length is greater than 4 : " + count); 
        // Output: Count of fruits which length is greater than 4 : 3
    }
}

Explanation:

  • Filtering: We first filter the stream to keep only the fruits with a length greater than 4.
  • .count(): This is the terminal operation that returns how many items remain in the stream after filtering.

6. Sorting a List of Objects

Example we need to sort a list of Student objects based on their roll number.


import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

class Student {
    String name;
    int rollNumber;

    public Student(String name, int rollNumber) {
        this.name = name;
        this.rollNumber = rollNumber;
    }

    public String getName() {
        return name;
    }

    public int getRollNumber() {
        return rollNumber;
    }

    @Override
    public String toString() {
        return "Student{name='" + name + "', rollNumber=" + rollNumber + '}';
    }
}

public class Main {
    public static void main(String[] args) {
        List<Student> studentList = new ArrayList<>();
        studentList.add(new Student("Ravi", 205));
        studentList.add(new Student("Sneha", 202));
        studentList.add(new Student("Amit", 208));
        studentList.add(new Student("Priya", 200));

        List<Student> sortedByRollNumber = studentList.stream()
                .sorted(Comparator.comparingInt(Student::getRollNumber))
                .collect(Collectors.toList());

        System.out.println(sortedByRollNumber);
        // Output: [Student{name='Priya', rollNumber=200}, Student{name='Sneha', rollNumber=202}, Student{name='Ravi', rollNumber=205}, Student{name='Amit', rollNumber=208}]
    }
}

Explanation:

  • .sorted(Comparator.comparingInt(Student::getRollNumber)): This is an intermediate operation that sorts the stream of Student objects.
  • Comparator.comparingInt(Student::getRollNumber) creates a Comparator that compares Student objects based on their rollNumber obtained using the getRollNumber() method. The comparingInt method is used because rollNumber is an integer.
  • .collect(Collectors.toList()): This terminal operation collects the sorted Student objects from the stream and returns a new List containing them.

7. Finding the Maximum Value in a List

Example: Now we need to find the maximum value that is present in a list of integers.


import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class FindMaxScore {
    public static void main(String[] args) {
        List<Integer> scores = Arrays.asList(85, 92, 78, 95, 88);

        Optional<Integer> maxScore = scores.stream()
                                            .max(Integer::compare);

        maxScore.ifPresent(score -> System.out.println("The maximum score is: " + score));
        // Output: The maximum score is: 95

        // Example with an empty list
        List<Integer> emptyScores = Arrays.asList();
        Optional<Integer> maxEmptyScore = emptyScores.stream()
                                                   .max(Integer::compare);
        System.out.println("Maximum score in an empty list: " + maxEmptyScore);
        // Output: Maximum score in an empty list: Optional.empty
    }
}

Explanation:

  • .max(Integer::compare): This is a terminal operation that finds the maximum element in the stream using the natural ordering of integers (defined by Integer::compare).
  • It returns an Optional<Integer> to handle the case where the stream might be empty, and thus there would be no maximum value.
  • .ifPresent(score -> System.out.println(...)): This part of the code checks if a value is present in the Optional. If it is, the provided lambda expression (which prints the maximum score) is executed.

8. Grouping Elements by a Property

Example: Create a grouping of a list of Person objects by their age.


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

class Person {
    String name;
    int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

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

public class GroupByAge {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Alice", 30),
                new Person("Bob", 25),
                new Person("Charlie", 35),
                new Person("David", 30)
        );

        Map<Integer, List<Person>> peopleByAge = people.stream()
                .collect(Collectors.groupingBy(Person::getAge));

        System.out.println(peopleByAge);
        // Output: {25=[Person{name='Bob', age=25}], 30=[Person{name='Alice', age=30}, Person{name='David', age=30}], 35=[Person{name='Charlie', age=35}]}
    }
}

Explanation:

  • .collect(Collectors.groupingBy(Person::getAge)): This is the terminal operation that groups the Person objects in the stream based on their age.
  • Collectors.groupingBy(Person::getAge) creates a Collector that organizes the elements into a Map.
  • In the resulting Map, each unique age becomes a key, and the value associated with that key is a List of all Person objects having that specific age.

9. Checking if Any Element Matches a Condition

Example: Now we need to check if there is any string present in the list that starts with the letter “b”.


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

public class AnyMatchExample {
    public static void main(String[] args) {
        List<String> animals = Arrays.asList("cat", "dog", "bird", "elephant");

        boolean startsWithB = animals.stream()
                                    .anyMatch(animal -> animal.startsWith("b"));

        System.out.println("Does any animal start with 'b'? " + startsWithB);
        // Output: Does any animal start with 'b'? true

        List<String> fruits = Arrays.asList("apple", "orange", "grape");
        boolean startsWithBInFruits = fruits.stream()
                                          .anyMatch(fruit -> fruit.startsWith("b"));
        System.out.println("Does any fruit start with 'b'? " + startsWithBInFruits);
        // Output: Does any fruit start with 'b'? false
    }
}

Explanation:

  • .anyMatch(animal -> animal.startsWith("b")): This is a terminal operation that checks if at least one element in the stream matches the given predicate.
  • The lambda expression animal -> animal.startsWith("b") is the predicate. It takes each animal string from the stream and checks if it starts with the letter “b”.
  • The anyMatch() operation returns true as soon as it finds an element that satisfies the predicate. If it goes through the entire stream without finding a match, it returns false.

10. Flattening a List of Lists

Problem: Given a list of lists of integers, flatten it into a single list.


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

public class FlattenList {
    public static void main(String[] args) {
        List<List<Integer>> listOfLists = Arrays.asList(
                Arrays.asList(10, 20, 30),
                Arrays.asList(40, 50),
                Arrays.asList(60, 70, 80, 90)
        );

        List<Integer> flattenedNumbersList = listOfLists.stream()
                .flatMap(List::stream)
                .collect(Collectors.toList());

        System.out.println(flattenedNumbersList);
        // Output: [10, 20, 30, 40, 50, 60, 70, 80, 90]

        List<List<String>> listOfStringLists = Arrays.asList(
                Arrays.asList("a", "b"),
                Arrays.asList("c"),
                Arrays.asList("d", "e", "f")
        );

        List<String> flattenedStringList = listOfStringLists.stream()
                .flatMap(List::stream)
                .collect(Collectors.toList());

        System.out.println(flattenedStringList);
        // Output: [a, b, c, d, e, f]
    }
}

Explanation:

  • .flatMap(List::stream): This is an intermediate operation that transforms each inner list within the listOfLists into a separate Stream of its elements.
  • The flatMap operation then flattens these individual streams into a single, continuous stream of integers. Effectively, it takes a stream of lists and converts it into a stream of the elements contained within those lists.
  • .collect(Collectors.toList()): This terminal operation collects all the elements from the flattened stream and gathers them into a new List<Integer> called flattenedNumbersList.

Tips for Mastering the Stream API in Java

  • Practice Regularly: The more you work with streams, the more comfortable you’ll become. Try solving various problems with different data structures and operations.
  • Understand Intermediate vs. Terminal Operations: Knowing the difference is crucial for building correct stream pipelines.
  • Leverage Lambda Expressions and Method References: They make your stream code concise and readable.
  • Explore the Collectors Class: This class provides a wealth of useful terminal operations for collecting stream elements into different data structures.
  • Consider Performance: While streams often improve readability, be mindful of potential performance implications in very complex scenarios.

Conclusion

The Stream API is a powerful feature that was introduced by java 8 for processing and performing operations on the data efficiently and expressively. By working through these practice problems and understanding the core concepts of stream api in java, you’ll be well on your way to mastering this essential feature of modern Java. Keep practicing and explore the vast capabilities of the Stream API, and enjoy writing cleaner, readable, and more efficient code!

Leave a Comment