Java Trainer

πŸŽ“ Java Learning Module

Flow Like Water: Mastering Java Streams

πŸ‘‹ Meet Your Java Trainer!

Hi! I'm Remsey, your Java instructor. Ready to flow through data with Streams!

🎧 Listen to the Audio Version

Prefer to learn by listening? Hit play below and watch the audio come to life!

70%
0:00 / 0:00

Welcome to Java Streams β€” the functional programming powerhouse that makes working with collections feel like magic! 🌊✨

Imagine you have a huge pile of data β€” names, numbers, objects. Instead of writing messy loops and if-statements, Streams let you say: "Filter this, map that, and give me the result!" 🎯

Streams turn collections into data pipelines that are clean, readable, and incredibly powerful.

What Are Streams?

A Stream in Java is a sequence of elements from a source (like a List or Array) that supports aggregate operations. Think of it like a conveyor belt in a factory β€” data flows through, gets transformed, and comes out the other end! 🏭

Let's start with a simple example β€” filtering numbers:

πŸ”’ Basic Stream - Filter Even Numbers
import java.util.Arrays;
import java.util.List;

public class StreamBasics {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // Old way - with for loop 😴
        System.out.println("Old way:");
        for (Integer num : numbers) {
            if (num % 2 == 0) {
                System.out.print(num + " ");
            }
        }
        
        // Stream way - clean and beautiful! 🌟
        System.out.println("\n\nStream way:");
        numbers.stream()
               .filter(n -> n % 2 == 0)
               .forEach(n -> System.out.print(n + " "));
    }
}
πŸ“€ Expected Output:
Old way:
2 4 6 8 10
Stream way:
2 4 6 8 10

Stream Operations

Streams have two types of operations:

🎯 Map - Transform Data
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", "diana");
        
        // Convert all names to uppercase
        List<String> upperNames = names.stream()
            .map(String::toUpperCase)
            .collect(Collectors.toList());
        
        System.out.println("Original: " + names);
        System.out.println("Uppercase: " + upperNames);
        
        // Get name lengths
        List<Integer> nameLengths = names.stream()
            .map(String::length)
            .collect(Collectors.toList());
        
        System.out.println("Lengths: " + nameLengths);
    }
}
πŸ“€ Expected Output:
Original: [alice, bob, charlie, diana]
Uppercase: [ALICE, BOB, CHARLIE, DIANA]
Lengths: [5, 3, 7, 5]

Filter + Map + Collect

The real power comes from chaining operations together! Let's build a pipeline:

⛓️ Chaining Stream Operations
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamChaining {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList(
            "apple", "banana", "cherry", "avocado", 
            "apricot", "blueberry", "kiwi"
        );
        
        // Get fruits starting with 'a', make uppercase, sort, collect
        List<String> result = fruits.stream()
            .filter(f -> f.startsWith("a"))    // Filter
            .map(String::toUpperCase)          // Transform
            .sorted()                           // Sort
            .collect(Collectors.toList());  // Collect
        
        System.out.println("🍎 Fruits starting with 'a':");
        result.forEach(f -> System.out.println("  - " + f));
    }
}
πŸ“€ Expected Output:
🍎 Fruits starting with 'a':
- APPLE
- APRICOT
- AVOCADO

Working with Objects

Streams really shine when working with objects! Let's create a Person class and filter/sort people:

πŸ‘₯ Streams with Objects
import java.util.Arrays;
import java.util.List;
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 toString() {
        return name + " (" + age + ") ";
    }
}

public class PersonStream {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 25),
            new Person("Bob", 30),
            new Person("Charlie", 22),
            new Person("Diana", 28)
        );
        
        // Find people over 25, sort by age
        System.out.println("πŸ‘€ People over 25 (sorted by age):");
        people.stream()
            .filter(p -> p.age > 25)
            .sorted((p1, p2) -> Integer.compare(p1.age, p2.age))
            .forEach(System.out::println);
        
        // Get list of names only
        List<String> names = people.stream()
            .map(p -> p.name)
            .collect(Collectors.toList());
        
        System.out.println("\nπŸ“ All names: " + names);
    }
}
πŸ“€ Expected Output:
πŸ‘€ People over 25 (sorted by age):
Diana (28)
Bob (30)
πŸ“ All names: [Alice, Bob, Charlie, Diana]

Powerful Terminal Operations

Terminal operations give you the final result. Here are some game-changers:

🎯 Count, Sum, Average, Min, Max
import java.util.Arrays;
import java.util.List;
import java.util.OptionalDouble;

public class TerminalOps {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 10, 15, 20, 25, 30);
        
        // Count
        long count = numbers.stream().count();
        System.out.println("Count: " + count);
        
        // Sum
        int sum = numbers.stream()
            .mapToInt(Integer::intValue)
            .sum();
        System.out.println("Sum: " + sum);
        
        // Average
        OptionalDouble avg = numbers.stream()
            .mapToInt(Integer::intValue)
            .average();
        System.out.println("Average: " + avg.orElse(0));
        
        // Max
        int max = numbers.stream()
            .mapToInt(Integer::intValue)
            .max()
            .orElse(0);
        System.out.println("Max: " + max);
        
        // Min
        int min = numbers.stream()
            .mapToInt(Integer::intValue)
            .min()
            .orElse(0);
        System.out.println("Min: " + min);
    }
}
πŸ“€ Expected Output:
Count: 6
Sum: 105
Average: 17.5
Max: 30
Min: 5

🧩 Mini-Challenge

You have a list of products with prices. Filter products under $50, get their names in uppercase, and collect them into a list!

πŸ’ͺ Challenge - Product Filter
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Product {
    String name;
    double price;
    
    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }
}

public class ProductChallenge {
    public static void main(String[] args) {
        List<Product> products = Arrays.asList(
            new Product("laptop", 899.99),
            new Product("mouse", 25.50),
            new Product("keyboard", 45.00),
            new Product("monitor", 199.99),
            new Product("usb cable", 9.99)
        );
        
        // TODO: Filter products under $50
        // TODO: Map to uppercase names
        // TODO: Collect to list
        // TODO: Print the result
    }
}

πŸ’ͺ Practice Exercises

Master Streams with these hands-on exercises!

πŸ”’ Exercise 1: Sum of Squares

Task: Given a list of integers, use Streams to calculate the sum of squares of all even numbers.

πŸ’‘ Hints
  • Use filter() to get even numbers
  • Use map() to square each number
  • Use reduce() or sum() to add them up
βœ… Solution
πŸ”’ Exercise 1 Solution
import java.util.Arrays;
import java.util.List;

public class SumOfSquares {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        int sumOfSquares = numbers.stream()
            .filter(n -> n % 2 == 0)     // Even numbers only
            .map(n -> n * n)              // Square them
            .mapToInt(Integer::intValue)  // Convert to int stream
            .sum();                       // Sum them up
        
        System.out.println("πŸ“Š Sum of squares of even numbers: " + sumOfSquares);
        System.out.println("Calculation: 2Β² + 4Β² + 6Β² + 8Β² + 10Β² = 4 + 16 + 36 + 64 + 100 = " + sumOfSquares);
    }
}

πŸ“š Exercise 2: Group Words by Length

Task: Given a list of words, group them by their length using Streams and Collectors.groupingBy().

πŸ’‘ Hints
  • Use Collectors.groupingBy()
  • Group by String::length
  • Result will be a Map<Integer, List<String>>
βœ… Solution
πŸ“š Exercise 2 Solution
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class GroupByLength {
    public static void main(String[] args) {
        List<String> words = Arrays.asList(
            "Java", "is", "awesome", "I", "love", 
            "coding", "in", "Java", "Streams"
        );
        
        Map<Integer, List<String>> grouped = words.stream()
            .collect(Collectors.groupingBy(String::length));
        
        System.out.println("πŸ“– Words grouped by length:");
        grouped.forEach((length, wordList) -> {
            System.out.println("  Length " + length + ": " + wordList);
        });
    }
}

πŸ’° Exercise 3: Top 3 Salaries

Task: Given a list of employees with salaries, find the top 3 highest-paid employees using Streams.

πŸ’‘ Hints
  • Use sorted() with a custom comparator
  • Use limit(3) to get top 3
  • Sort in descending order using Comparator.reverseOrder()
βœ… Solution
πŸ’° Exercise 3 Solution
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

class Employee {
    String name;
    double salary;
    
    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
    
    public String toString() {
        return name + ": $" + salary;
    }
}

public class Top3Salaries {
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
            new Employee("Alice", 75000),
            new Employee("Bob", 95000),
            new Employee("Charlie", 65000),
            new Employee("Diana", 120000),
            new Employee("Eve", 88000),
            new Employee("Frank", 102000)
        );
        
        System.out.println("πŸ† Top 3 Highest Paid Employees:");
        employees.stream()
            .sorted(Comparator.comparingDouble((e -> e.salary)).reversed())
            .limit(3)
            .forEach(e -> System.out.println("  " + e));
    }
}

πŸ’‘ Pro Tip: Streams make your code more readable and maintainable! Once you get the hang of them, you'll never want to write loops again. They're also optimized for parallel processing! πŸš€

←

PREVIOUS MODULE

Generics

NEXT MODULE

Coming Soon...

β†’