A Complete Beginner’s Guide to Java Stream API with Detailed Examples

Krishan
Krishan Follow
5 min read·January 1, 2024

0
0
Responses
Bookmark
Share

A Complete Beginner’s Guide to Java Stream API with Detailed Examples

Java 8 brought many exciting features to the language, and the Stream API is one of its most powerful additions. Streams provide a clean and efficient way to process collections of data, like filtering, transforming, and aggregating elements. With the Stream API, you can write shorter, more readable, and more functional-style code.

In this blog, we’ll explain what the Stream API is, how it works, and break down the most important operations with detailed examples that even a beginner can easily follow.


What is the Java Stream API?

Before diving into the operations, let's first understand the Stream API. Imagine you have a list of items, like groceries or books, and you want to:

  • Pick only certain items (filter).
  • Transform them into something else (map).
  • Count them, sort them, or combine them.

The Stream API helps you do these tasks easily, step by step, without writing long loops or complex logic.


How Does the Stream API Work?

Streams work like an assembly line in a factory. Data goes through a series of steps (operations) and gets processed. There are two types of steps:

  1. Intermediate Steps: These are like stations in the assembly line (e.g., filtering or transforming data). They don’t give the final output but prepare the data for the next step.
  2. Final Step: This is where the assembly line produces the final product (e.g., a list of results).

  3. Benefits of the Stream API

    • Declarative Code: Write what you want to achieve instead of how to do it.
    • Shorter Code: Avoid long and repetitive loops.
    • Lazy Evaluation: Intermediate operations (like filter) are not performed until a terminal operation (like collect) is invoked.
    • Parallel Processing: Streams can easily run on multiple threads using parallelStream() for better performance on large datasets.

Stream API Key Operations (Explained for Beginners)

Let’s break down the most commonly used Stream API operations with simple explanations, analogies, and examples.


1. Filter: Picking What You Need

What It Does:
filter() is like a sieve. It goes through each item in a collection and keeps only the ones that meet a condition. For example, imagine you’re picking only red apples from a basket of fruits.

Example:

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

public class FilterExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

        // Keep only even numbers
        List<Integer> evenNumbers = numbers.stream()
                                           .filter(num -> num % 2 == 0)
                                           .collect(Collectors.toList());

        System.out.println("Even Numbers: " + evenNumbers); // Output: [2, 4, 6]
    }
}

Explanation:

  1. Input: A list of numbers [1, 2, 3, 4, 5, 6].
  2. Condition: The filter keeps only numbers divisible by 2 (num % 2 == 0).
  3. Output: A new list [2, 4, 6].

2. Map: Transforming Data

What It Does:
map() is like a translator. It converts each item in a list into something else. For example, turning a list of lowercase names into uppercase names.

Real-Life Analogy:
Imagine you’re converting prices from dollars to rupees (multiplying each price by 75).

Example:

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

public class MapExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("alice", "bob", "charlie");

        // Convert names to uppercase
        List<String> upperCaseNames = names.stream()
                                           .map(String::toUpperCase)
                                           .collect(Collectors.toList());

        System.out.println("Uppercase Names: " + upperCaseNames); // Output: [ALICE, BOB, CHARLIE]
    }
}

Explanation:

  1. Input: A list of names [alice, bob, charlie].
  2. Transformation: Each name is converted to uppercase using String::toUpperCase.
  3. Output: A new list [ALICE, BOB, CHARLIE].

3. Sorted: Arranging Items

What It Does:
sorted() puts the elements in order, like arranging books alphabetically or numbers from smallest to largest.

Example:

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

public class SortedExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 4);

        // Sort numbers in ascending order
        List<Integer> sortedNumbers = numbers.stream()
                                             .sorted()
                                             .collect(Collectors.toList());

        System.out.println("Sorted Numbers: " + sortedNumbers); // Output: [1, 3, 4, 5, 8]
    }
}

Explanation:

  1. Input: A list of numbers [5, 3, 8, 1, 4].
  2. Action: The sorted() method arranges them in ascending order.
  3. Output: A new list [1, 3, 4, 5, 8].

4. Distinct: Removing Duplicates

What It Does:
distinct() removes duplicate items from a collection. It’s like picking only unique colors from a box of crayons.

Example:

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

public class DistinctExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);

        // Remove duplicates
        List<Integer> distinctNumbers = numbers.stream()
                                               .distinct()
                                               .collect(Collectors.toList());

        System.out.println("Distinct Numbers: " + distinctNumbers); // Output: [1, 2, 3, 4, 5]
    }
}

5. Collect: Getting the Final Result

What It Does:
collect() gathers the processed data into a collection, such as a List, Set, or Map.

Example:

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

public class CollectExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

        // Collect names into a list
        List<String> collectedNames = names.stream()
                                           .collect(Collectors.toList());

        System.out.println("Collected Names: " + collectedNames); // Output: [Alice, Bob, Charlie]
    }
}

6. Reduce: Combining Everything

What It Does:
reduce() combines all the elements into a single value, like summing up all the numbers in a list.

Real-Life Analogy:
Imagine you’re adding up your grocery bill to get the total cost.

Example:

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

public class ReduceExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // Calculate the sum of all numbers
        int sum = numbers.stream()
                         .reduce(0, Integer::sum);

        System.out.println("Sum: " + sum); // Output: 15
    }
}

7. forEach: Doing Something for Each Item

What It Does:
forEach() performs an action on each item in the stream. It’s often used to print or log data.

Example:

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

public class ForEachExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

        // Print each name
        names.stream()
             .forEach(System.out::println);
    }
}

8. Parallel Streams: Faster Processing

What It Does:
Parallel streams process data across multiple threads, making it faster for large datasets.

Example:

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

public class ParallelStreamExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        // Process names in parallel
        names.parallelStream()
             .forEach(System.out::println); // Order is not guaranteed
    }
}

How to Combine Stream Operations

You can combine multiple operations to process data step by step.

Example: Filtering, Mapping, and Collecting

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

public class CombinedStreamExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        // Filter names starting with 'A', convert to uppercase, and collect
        List<String> result = names.stream()
                                   .filter(name -> name.startsWith("A"))
                                   .map(String::toUpperCase)
                                   .collect(Collectors.toList());

        System.out.println(result); // Output: [ALICE]
    }
}

  1. Practical Use Cases of Stream API

  2. Filter Data:
    Example: Get employees with salaries greater than 50,000.

  3. Transform Data:
    Example: Convert raw scores into percentages.

  4. Aggregate Data:
    Example: Find the total sales for a product.

  5. Process Large Files:
    Example: Count the number of lines in a log file.


Stream API: Key Points to Remember

  • Streams do not store data; they process it.
  • Intermediate operations (like filter, map) are lazy and executed only when a terminal operation (like collect) is called.
  • Streams cannot be reused; once processed, you must create a new stream.

Conclusion

The Stream API in Java 8 simplifies data processing by making it declarative and concise. Think of it as a way to process data step-by-step, just like an assembly line. By mastering basic operations like filter, map, sorted, and reduce, you can handle collections of data in a clean and efficient way.

Start practicing the examples, and you’ll see how useful and intuitive the Stream API is for solving real-world problems!

If something seems incorrect or needs adjustment, please let me know!

Comments

Blog Categories