Stream API — map, filter, reduce, collect

Stream API — data-কে command-এর বদলে describe করে processing

Read: ~45 min Advanced 5 practice problems Live code runner

1. Declarative, Lazy, Maybe Parallel

A Stream is a pipeline over a source (collection, array, file, generator). You describe the transformations — map, filter, sorted — and Java runs them lazily, combining steps, only when a terminal operation like collect or reduce is invoked.

Stream হলো একটি pipeline — source থেকে শুরু, মাঝে transformation (map, filter, sorted), শেষে terminal operation (collect, reduce)। Intermediate step lazy — terminal না আসা পর্যন্ত কিছু চলে না।
Stream Pipeline source list / array filter Predicate map Function sorted Comparator collect / reduce terminal Intermediate ops are lazy · terminal op triggers execution Figure 29.1 — source → intermediate (lazy) → terminal।

2. map and filter

filter removes elements that don't match a Predicate; map transforms each element via a Function.

filter match না করা element-গুলো সরিয়ে দেয়; map প্রতিটি element-কে Function দিয়ে রূপান্তর করে।
Main.java
import java.util.*;
import java.util.stream.*;

class Main {
    public static void main(String[] args) {
        List<String> cities = List.of("Dhaka", "Chattogram", "Sylhet", "Rajshahi", "Khulna");

        List<String> bigUpper = cities.stream()
            .filter(c -> c.length() > 6)
            .map(String::toUpperCase)
            .toList();

        System.out.println(bigUpper);
    }
}

3. reduce — Folding a Stream into a Value

reduce collapses a stream into a single value using an associative binary operator. Common reductions (sum, max, count) have named shortcuts on primitive streams.

reduce একটি associative binary operator দিয়ে stream-কে এক-value-তে পরিণত করে। sum/max/count-এর মতো সাধারণ reduction-এ primitive stream-এ shortcut আছে।
Main.java
import java.util.*;
import java.util.stream.*;

class Main {
    public static void main(String[] args) {
        List<Integer> xs = List.of(1, 2, 3, 4, 5);

        int sum = xs.stream().mapToInt(Integer::intValue).sum();
        int prod = xs.stream().reduce(1, (a, b) -> a * b);
        int max = xs.stream().mapToInt(Integer::intValue).max().orElse(Integer.MIN_VALUE);

        System.out.println("sum  = " + sum);
        System.out.println("prod = " + prod);
        System.out.println("max  = " + max);
    }
}

4. collect and Collectors

collect turns a stream into a concrete data structure. The Collectors factory gives you toList, toSet, toMap, joining, groupingBy, partitioningBy, and more.

collect stream-কে একটি concrete data structure-এ ফিরিয়ে দেয়। Collectors-এ toList, toSet, toMap, joining, groupingBy, partitioningBy-সহ অনেক কিছু আছে।
Main.java
import java.util.*;
import java.util.stream.*;

class Main {
    record Student(String name, String dept, int marks) {}

    public static void main(String[] args) {
        List<Student> xs = List.of(
            new Student("Arif",  "CSE", 88),
            new Student("Rina",  "CSE", 92),
            new Student("Hasan", "EEE", 76),
            new Student("Maya",  "EEE", 81),
            new Student("Nabil", "CSE", 65)
        );

        // joining
        String names = xs.stream().map(Student::name).collect(Collectors.joining(", "));
        System.out.println("names = " + names);

        // groupingBy dept -> list of students
        Map<String, List<Student>> byDept =
            xs.stream().collect(Collectors.groupingBy(Student::dept));
        byDept.forEach((k, v) -> System.out.println(k + " -> " + v.size()));

        // averagingInt per dept
        Map<String, Double> avg =
            xs.stream().collect(Collectors.groupingBy(Student::dept, Collectors.averagingInt(Student::marks)));
        System.out.println("avg marks by dept = " + avg);

        // partitioningBy passed/failed (>= 70)
        Map<Boolean, List<Student>> passed =
            xs.stream().collect(Collectors.partitioningBy(s -> s.marks() >= 70));
        System.out.println("passed: " + passed.get(true).size());
        System.out.println("failed: " + passed.get(false).size());
    }
}

5. Laziness & Parallel Streams

Intermediate operations are lazy — nothing happens until a terminal op runs. Short-circuit operations like findFirst, anyMatch, limit stop as soon as they can. parallelStream() spreads work across the common fork-join pool — great for CPU-bound workloads on large data, but not a free win.

Intermediate operation lazy — terminal op না আসা পর্যন্ত কিছু হয় না। findFirst, anyMatch, limit short-circuit — যত দ্রুত possible তত দ্রুত থেমে যায়। parallelStream() কাজ multiple core-এ ছড়িয়ে দেয় — বড় CPU-bound কাজে কাজে লাগে, কিন্তু সব সময় দ্রুত নয়।
Main.java
import java.util.*;
import java.util.stream.*;

class Main {
    public static void main(String[] args) {
        // Lazy + short-circuit: find first even greater than 100
        int found = IntStream.rangeClosed(1, 1_000_000)
            .filter(n -> n > 100 && n % 2 == 0)
            .findFirst()
            .orElse(-1);
        System.out.println("found = " + found);

        // Parallel: sum 1..1_000_000
        long sum = IntStream.rangeClosed(1, 1_000_000).parallel().sum();
        System.out.println("parallel sum = " + sum);
    }
}

6. Vocabulary

TermMeaningবাংলায়
StreamLazy pipeline over a source.source-এর উপর lazy pipeline।
Intermediate opmap, filter, sorted — lazy, returns a Stream.Lazy; আরেকটি Stream দেয়।
Terminal opcollect, reduce, forEach — triggers execution.এটি এলেই সব চলে।
Short-circuitStops as soon as possible (anyMatch, limit).যত দ্রুত সম্ভব থামে।
CollectorsFactory of terminal collectors.terminal collector-এর factory।
parallelStreamUses common fork-join pool.Fork-join pool-এ parallel execution।
Gotcha: streams are single-use. After a terminal op, the stream is closed — reusing it throws IllegalStateException.

Stream single-use — terminal op-এর পর close; আবার ব্যবহার করলে IllegalStateException।

7. Practice Problems

  1. Sum all even numbers from 1 to 100 using a stream.
    1 থেকে 100-এর মধ্যে সব জোড় সংখ্যার যোগফল stream দিয়ে বের করুন।
    ✨ Show Answer
    Main.java
    import java.util.stream.*;
    class Main {
        public static void main(String[] args) {
            int sum = IntStream.rangeClosed(1, 100)
                .filter(n -> n % 2 == 0).sum();
            System.out.println(sum);
        }
    }
  2. Given a list of words, return a new list of their uppercased versions longer than 4 characters.
    কিছু শব্দের list থেকে 4-এর চেয়ে বড় উপরকেস version-এর নতুন list বের করুন।
    ✨ Show Answer
    Main.java
    import java.util.*;
    class Main {
        public static void main(String[] args) {
            List<String> xs = List.of("go", "hello", "dhaka", "hi", "chattogram");
            List<String> out = xs.stream()
                .filter(s -> s.length() > 4)
                .map(String::toUpperCase)
                .toList();
            System.out.println(out);
        }
    }
  3. Group a list of words by their first letter using Collectors.groupingBy.
    কিছু শব্দকে প্রথম অক্ষরের ভিত্তিতে groupingBy দিয়ে group করুন।
    ✨ Show Answer
    Main.java
    import java.util.*;
    import java.util.stream.*;
    class Main {
        public static void main(String[] args) {
            List<String> xs = List.of("apple", "ant", "banana", "berry", "cherry");
            Map<Character, List<String>> g =
                xs.stream().collect(Collectors.groupingBy(s -> s.charAt(0)));
            System.out.println(g);
        }
    }
  4. Explain in your words what "lazy evaluation" means for streams.
    Stream-এ "lazy evaluation" বলতে কী বোঝায় — নিজের ভাষায় লিখুন।
    ✨ Show Answer

    Answer: Intermediate operations on a stream record what to do but don't actually do it. The whole pipeline starts running only when a terminal operation asks for a result, and the JVM fuses the steps so it usually passes each element through the whole pipeline once. This enables short-circuit ops (findFirst, limit) to skip unnecessary work on huge datasets.

    Intermediate operation শুধু "কী করতে হবে" record করে, চালায় না। Terminal operation আসলেই পুরো pipeline চালু হয়; JVM step-গুলো fuse করে, সাধারণত প্রতি element এক-বারই পুরো pipeline দিয়ে যায়। এর ফলে findFirst/limit-এর মতো short-circuit op বিশাল dataset-এ অপ্রয়োজনীয় কাজ এড়িয়ে যেতে পারে।

  5. From a list of Student(name, marks), compute the name of the top scorer with a stream.
    কিছু Student(name, marks) থেকে সর্বোচ্চ নম্বর পাওয়া ছাত্রের নাম বের করুন।
    ✨ Show Answer
    Main.java
    import java.util.*;
    class Main {
        record Student(String name, int marks) {}
        public static void main(String[] args) {
            List<Student> xs = List.of(
                new Student("Arif", 88),
                new Student("Rina", 92),
                new Student("Hasan", 76)
            );
            String top = xs.stream()
                .max(Comparator.comparingInt(Student::marks))
                .map(Student::name).orElse("?");
            System.out.println("top = " + top);
        }
    }

Summary — Module 29

A Stream is a lazy pipeline: source → intermediate ops (map, filter, sorted) → terminal op (collect, reduce, forEach). Collectors gives you toList, toMap, groupingBy, partitioningBy, and joining. Use parallelStream() only for large, CPU-bound, non-I/O-bound workloads.

Stream একটি lazy pipeline — source → intermediate (map/filter/sorted) → terminal (collect/reduce/forEach)। Collectors-এ toList, toMap, groupingBy, partitioningBy, joining আছে। বড় CPU-bound কাজেই parallelStream() বেছে নিন।

Next Module → Optional — NullPointerException থেকে বাঁচার type-safe উপায়।