Modern Java

Filtering, Mapping, and Transforming Streams

ReadingPreview

আপনি একটি free preview lesson দেখছেন।

Lesson Overview

আগের lesson-এ আমরা Stream API-এর basic structure শিখেছি।

একটি সাধারণ pipeline:

courses.stream()
        .filter(
                Course::published
        )
        .map(
                Course::title
        )
        .toList();

এখানে flow ছিল:

Source
→ Filter
→ Transform
→ Result

কিন্তু বাস্তব application-এ pipeline অনেক সময় আরও complex হয়।

আমাদের প্রয়োজন হতে পারে:

একাধিক condition apply করা
nested collections flatten করা
duplicate values বাদ দেওয়া
values sort করা
প্রথম কয়েকটি result নেওয়া
কিছু result skip করা
প্রথম matching value খোঁজা
কোনো value condition match করে কি না দেখা
সব value condition match করে কি না দেখা

এই lesson-এ আমরা শিখব:

  • Multiple filter() operations
  • Complex filtering
  • map()
  • Chained transformations
  • flatMap()
  • Nested collections flatten করা
  • distinct()
  • sorted()
  • Custom sorting with Comparator
  • limit()
  • skip()
  • findFirst()
  • anyMatch()
  • allMatch()
  • noneMatch()
  • Short-circuiting
  • Pipeline ordering
  • Stream operation order কেন গুরুত্বপূর্ণ
  • Common mistakes
  • Practical transformation patterns

Start with a Domain Model

এই lesson-এর examples-এর জন্য আমরা একটি simple Course model ব্যবহার করব।

record Course(
        String code,
        String title,
        long priceInPaisa,
        boolean published,
        List<String> topics
) {
}

Example data:

List<Course> courses =
        List.of(
                new Course(
                        "JAVA",
                        "Java Foundation",
                        300_000,
                        true,
                        List.of(
                                "Java",
                                "OOP",
                                "Programming"
                        )
                ),
                new Course(
                        "BACKEND",
                        "Backend Development",
                        500_000,
                        true,
                        List.of(
                                "Java",
                                "Spring",
                                "Backend"
                        )
                ),
                new Course(
                        "SYSTEM-DESIGN",
                        "System Design",
                        800_000,
                        false,
                        List.of(
                                "Architecture",
                                "Scaling",
                                "Backend"
                        )
                )
        );

Multiple Filters

Suppose আমরা শুধু এমন Course চাই যেগুলো:

published
AND
price >= 400000

একটি filter() দিয়ে:

List<Course> result =
        courses.stream()
                .filter(
                        course ->
                                course.published()
                                && course.priceInPaisa()
                                >= 400_000
                )
                .toList();

এটি valid।


Separate Filters

একই logic দুইটি filter-এ ভাগ করা যায়:

List<Course> result =
        courses.stream()
                .filter(
                        Course::published
                )
                .filter(
                        course ->
                                course.priceInPaisa()
                                >= 400_000
                )
                .toList();

এখন pipeline read করা যায়:

published Course রাখো
তারপর 400000 বা তার বেশি price-এর Course রাখো

One Filter or Multiple Filters?

কোনটি ব্যবহার করবেন তা readability-এর উপর depend করে।

যদি condition closely related এবং simple হয়:

.filter(
        course ->
                course.published()
                && course.priceInPaisa()
                > 0
)

ভালো হতে পারে।

যদি conditions independently meaningful হয়:

.filter(
        Course::published
)
.filter(
        course ->
                course.priceInPaisa()
                > 0
)

আরও readable হতে পারে।


Named Predicates

যদি rules reused হয়:

Predicate<Course> published =
        Course::published;

Predicate<Course> premium =
        course ->
                course.priceInPaisa()
                >= 500_000;

Then:

List<Course> result =
        courses.stream()
                .filter(
                        published.and(
                                premium
                        )
                )
                .toList();

Filtering Should Answer One Question

filter()-এর mental model:

এই element থাকবে কি থাকবে না?

Example:

course ->
        course.published()

returns:

true
or
false

Avoid Side Effects in filter()

এমন code avoid করা ভালো:

.filter(
        course -> {
            System.out.println(
                    course.title()
            );

            processedCount++;

            return course.published();
        }
)

filter() ideally condition express করবে।

Logging/debugging occasionally useful হলেও production logic-এ filtering এবং unrelated mutation mix করলে reasoning কঠিন হয়।


map() Revisited

map() একটি element-কে অন্য value-তে transform করে।

Example:

List<String> titles =
        courses.stream()
                .map(
                        Course::title
                )
                .toList();

Type flow:

Stream<Course>
→ Stream<String>
→ List<String>

Chained map()

Suppose Course title-এর length চাই।

List<Integer> titleLengths =
        courses.stream()
                .map(
                        Course::title
                )
                .map(
                        String::length
                )
                .toList();

Type flow:

Course
→ String
→ Integer

Direct Transformation

এটিও possible:

List<Integer> titleLengths =
        courses.stream()
                .map(
                        course ->
                                course.title()
                                        .length()
                )
                .toList();

দুইটিই valid।

কোনটি clearer তা context-এর উপর depend করে।


Transforming to Another Domain Object

Suppose:

record CourseSummary(
        String code,
        String title
) {
}

Then:

List<CourseSummary> summaries =
        courses.stream()
                .map(
                        course ->
                                new CourseSummary(
                                        course.code(),
                                        course.title()
                                )
                )
                .toList();

এটি real backend code-এ common pattern:

Domain object
→ DTO / Summary / View model

Constructor Method Reference

যদি constructor parameters directly compatible হয়, method reference useful হতে পারে।

কিন্তু এখানে:

Course

থেকে constructor-এর দুইটি separate fields বের করতে হচ্ছে।

তাই Lambda clearer:

course ->
        new CourseSummary(
                course.code(),
                course.title()
        )

Method reference force করার দরকার নেই।


What Is flatMap()?

flatMap() প্রথমবার একটু confusing মনে হতে পারে।

এর core idea:

একটি element থেকে multiple elements বের হয়
এবং nested structure flatten করা হয়।

Suppose প্রতিটি Course-এর আছে:

List<String> topics

তাহলে:

Course
→ List<String>

Using map() First

List<List<String>> topics =
        courses.stream()
                .map(
                        Course::topics
                )
                .toList();

Result conceptually:

[
    [Java, OOP, Programming],
    [Java, Spring, Backend],
    [Architecture, Scaling, Backend]
]

এখানে nested List রয়েছে:

List<List<String>>

But What If We Want One Flat List?

Desired:

Java
OOP
Programming
Java
Spring
Backend
Architecture
Scaling
Backend

অর্থাৎ:

List<String>

not:

List<List<String>>

এখানে flatMap() useful।


flatMap() Example

List<String> topics =
        courses.stream()
                .flatMap(
                        course ->
                                course.topics()
                                        .stream()
                )
                .toList();

What Happened?

Each Course:

Course

becomes:

Stream<String>

তারপর সব ছোট Streams flatten হয়ে একটি single:

Stream<String>

হয়।

Conceptually:

Course 1
→ [Java, OOP, Programming]

Course 2
→ [Java, Spring, Backend]

Course 3
→ [Architecture, Scaling, Backend]

Flatten:

Java
OOP
Programming
Java
Spring
Backend
Architecture
Scaling
Backend

map() vs flatMap()

এটি মনে রাখা খুব important।

map()

একটি element:

T

কে একটি result:

R

তে transform করে।

Example:

Course
→ List<String>

Result:

Stream<List<String>>

flatMap()

একটি element থেকে একটি Stream of values তৈরি করে এবং nested Streams flatten করে।

Example:

Course
→ Stream<String>

Final:

Stream<String>

Mental Model

map()
→ transform

flatMap()
→ transform + flatten

Another Nested Example

Suppose:

List<List<Integer>> groups =
        List.of(
                List.of(
                        1,
                        2
                ),
                List.of(
                        3,
                        4
                )
        );

Using map():

Stream<List<Integer>>

Using:

groups.stream()
        .flatMap(
                List::stream
        )

we get:

Stream<Integer>

Values:

1
2
3
4

List::stream

This:

.flatMap(
        List::stream
)

conceptually means:

list ->
        list.stream()

distinct()

Suppose topics contain duplicates:

Java
OOP
Java
Backend
Backend

আমরা unique values চাই।

Use:

.distinct()

Example

List<String> uniqueTopics =
        courses.stream()
                .flatMap(
                        course ->
                                course.topics()
                                        .stream()
                )
                .distinct()
                .toList();

Possible result:

Java
OOP
Programming
Spring
Backend
Architecture
Scaling

How Does distinct() Know Values Are Equal?

distinct() logical equality ব্যবহার করে।

Object equality-এর ক্ষেত্রে:

equals()
hashCode()

relevant হতে পারে।

এখানে আগের hashing lesson-এর concepts useful।


Distinct with Custom Objects

Suppose দুইটি object logically equal কিন্তু equals() properly implemented নয়।

Then:

distinct()

আপনার expected duplicate removal নাও দিতে পারে।

Value equality correctly design করা important।


sorted()

Stream elements sort করা যায়:

.sorted()

Example:

List<String> titles =
        courses.stream()
                .map(
                        Course::title
                )
                .sorted()
                .toList();

Natural ordering ব্যবহার হবে।


Sorting Objects with Comparator

For Course:

List<Course> sorted =
        courses.stream()
                .sorted(
                        Comparator.comparingLong(
                                Course::priceInPaisa
                        )
                )
                .toList();

Descending Price

List<Course> sorted =
        courses.stream()
                .sorted(
                        Comparator.comparingLong(
                                Course::priceInPaisa
                        ).reversed()
                )
                .toList();

Does sorted() Mutate the Original List?

না।

Example:

List<Course> sorted =
        courses.stream()
                .sorted(...)
                .toList();

courses original order unchanged থাকে।

এই behavior:

courses.sort(...)

থেকে different।


List.sort() vs Stream sorted()

List.sort():

original List reorder করে

Stream:

stream().sorted().toList()

new result তৈরি করে এবং source List reorder করে না।


Choose Based on Intent

যদি original mutable List-টিই reorder করতে চান:

courses.sort(
        comparator
);

যদি sorted result চান কিন্তু original preserve করতে চান:

List<Course> sorted =
        courses.stream()
                .sorted(
                        comparator
                )
                .toList();

limit()

limit(n) প্রথম n elements পর্যন্ত Stream restrict করে।

Example:

List<Course> firstTwo =
        courses.stream()
                .limit(
                        2
                )
                .toList();

Maximum দুইটি element return করবে।


Top 3 Most Expensive Courses

List<Course> topThree =
        courses.stream()
                .sorted(
                        Comparator.comparingLong(
                                Course::priceInPaisa
                        ).reversed()
                )
                .limit(
                        3
                )
                .toList();

Flow:

সব Course
→ price descending sort
→ প্রথম 3টি
→ List

limit() Does Not Mean Top-K Efficiency

এটি important।

sorted(...)
.limit(
        3
)

সাধারণত sorting step-এর cost avoid করে না।

If input huge এবং শুধু top 3 দরকার, bounded PriorityQueue approach algorithmically better হতে পারে।

Recall:

Full sort
→ O(n log n)

Bounded heap
→ O(n log k)

Stream syntax algorithmic complexity magically improve করে না।


skip()

skip(n) প্রথম n elements বাদ দেয়।

Example:

List<Course> remaining =
        courses.stream()
                .skip(
                        2
                )
                .toList();

প্রথম দুইটি Course বাদ যাবে।


skip() + limit()

এগুলো pagination-like slicing-এর মতো ব্যবহার করা যায়।

Example:

List<Course> page =
        courses.stream()
                .skip(
                        20
                )
                .limit(
                        10
                )
                .toList();

Meaning:

প্রথম 20টি বাদ
পরের 10টি নাও

Important Pagination Warning

In-memory Stream pagination:

skip()
limit()

এবং database pagination একই বিষয় নয়।

যদি data database-এ থাকে, millions of rows memory-তে load করে তারপর:

skip()

করা poor design হতে পারে।

Database-level pagination ideally database query-তেই করতে হয়।


Stable Ordering Before Skip/Limit

Pagination-এর আগে deterministic ordering গুরুত্বপূর্ণ।

Example:

courses.stream()
        .sorted(
                Comparator.comparing(
                        Course::code
                )
        )
        .skip(
                offset
        )
        .limit(
                size
        )
        .toList();

Ordering ছাড়া repeated pagination inconsistent হতে পারে যদি source order itself stable না হয়।


findFirst()

Suppose প্রথম published Course খুঁজতে চাই।

Optional<Course> course =
        courses.stream()
                .filter(
                        Course::published
                )
                .findFirst();

Return type:

Optional<Course>

কারণ matching Course নাও থাকতে পারে।

Optional আমরা dedicated lesson-এ বিস্তারিত শিখব।


Why Not Return Null?

findFirst() absence explicitly represent করে:

Optional.empty()

rather than forcing caller to rely on null।


Basic Optional Use for Now

courses.stream()
        .filter(
                Course::published
        )
        .findFirst()
        .ifPresent(
                course ->
                        System.out.println(
                                course.title()
                        )
        );

findFirst() Can Short-Circuit

Suppose প্রথম matching element source-এর শুরুতেই আছে।

Stream:

.filter(...)
.findFirst()

তারপর বাকি সব elements process করার প্রয়োজন নাও হতে পারে।

এটাকে বলা হয়:

short-circuiting

What Is Short-Circuiting?

কিছু operations full Stream process না করেও answer দিতে পারে।

Examples:

findFirst()
anyMatch()
allMatch()
noneMatch()
limit()

এরা situation অনুযায়ী processing early stop করতে পারে।


anyMatch()

Question:

কমপক্ষে একটি element condition match করে কি?

Example:

boolean hasFreeCourse =
        courses.stream()
                .anyMatch(
                        course ->
                                course.priceInPaisa()
                                == 0
                );

anyMatch() Mental Model

একটি match পেলেই
answer true

তাই বাকি elements check করার প্রয়োজন নাও হতে পারে।


Example

Values:

10
20
30
40

Check:

value > 25

Flow:

10 → false
20 → false
30 → true

এখন:

true

জানা হয়ে গেছে।

40 check করা প্রয়োজন নাও হতে পারে।


allMatch()

Question:

সব elements condition satisfy করে কি?

Example:

boolean allPublished =
        courses.stream()
                .allMatch(
                        Course::published
                );

allMatch() Can Stop Early

যেই একটি false পাওয়া যায়:

সব match করে

এটি আর true হতে পারে না।

তাই processing stop করা যায়।


noneMatch()

Question:

একটিও condition match করে না কি?

Example:

boolean noFreeCourse =
        courses.stream()
                .noneMatch(
                        course ->
                                course.priceInPaisa()
                                == 0
                );

Match Operations Summary

anyMatch()
→ অন্তত একটি match?

allMatch()
→ সব match?

noneMatch()
→ একটিও match না?

Empty Stream Behavior

একটি subtle point।

For empty Stream:

Stream.<Integer>empty()

anyMatch(...) returns:

false

কারণ কোনো matching element নেই।

allMatch(...) returns:

true

কারণ এমন কোনো element নেই যেটি condition violate করেছে।

noneMatch(...) returns:

true

কারণ কোনো matching element নেই।


Why Is allMatch() True for Empty Stream?

Logic-এর ভাষায়:

সব elements condition satisfy করে

এই statement-এর বিরুদ্ধে কোনো counterexample নেই কারণ elements-ই নেই।

এটিকে বলা হয়:

vacuous truth

Foundation হিসেবে শুধু behaviorটি মনে রাখুন।


Operation Order Matters

Suppose:

courses.stream()
        .filter(
                Course::published
        )
        .limit(
                3
        )

means:

published Course খুঁজে
প্রথম 3টি published Course নাও

But:

courses.stream()
        .limit(
                3
        )
        .filter(
                Course::published
        )

means:

প্রথম 3টি Course নাও
তার মধ্যে published যেগুলো আছে রাখো

দুইটি result completely different হতে পারে।


Example

Input:

Course A → unpublished
Course B → unpublished
Course C → published
Course D → published
Course E → published

Pipeline 1:

.filter(
        Course::published
)
.limit(
        2
)

Result:

C
D

Pipeline 2:

.limit(
        2
)
.filter(
        Course::published
)

Result:

empty

Filter Before Expensive Operations

If possible, reducing data early can improve efficiency।

Example:

courses.stream()
        .filter(
                Course::published
        )
        .map(
                this::expensiveTransformation
        )
        .toList();

Only published Courses undergo expensive transformation।


Compare with Map First

courses.stream()
        .map(
                this::expensiveTransformation
        )
        .filter(...)

এখানে potentially every Course expensive transformation-এর মধ্য দিয়ে যাবে।

Logical correctness permitting:

cheap selective filtering early

often useful।


But Do Not Reorder Blindly

Operations শুধু performance-এর জন্য reorder করবেন না যদি semantics change হয়।

Example:

map
then filter

এবং:

filter
then map

same result নাও দিতে পারে।

Correctness first।


distinct() and Ordering

Suppose:

JAVA
BACKEND
JAVA
SYSTEM
BACKEND

Then:

stream.distinct()

encounter order preserve করতে পারে ordered sequential stream-এর ক্ষেত্রে।

Result:

JAVA
BACKEND
SYSTEM

কিন্তু code-এর real requirement যদি order-specific হয়, source/order semantics পরিষ্কার রাখা ভালো।


sorted() After distinct()

Unique sorted topics:

List<String> topics =
        courses.stream()
                .flatMap(
                        course ->
                                course.topics()
                                        .stream()
                )
                .distinct()
                .sorted()
                .toList();

Flow:

Course
→ topics
→ flatten
→ duplicates remove
→ alphabetical sort
→ List

Should We Sort Before Distinct?

This also produces unique sorted values:

.flatMap(...)
.sorted()
.distinct()

কিন্তু often:

distinct()
.sorted()

can reduce how many elements sorting needs to process when duplicates are common।

Correctness permitting, operation ordering can affect efficiency।


Example Pipeline — Published Unique Topics

List<String> topics =
        courses.stream()
                .filter(
                        Course::published
                )
                .flatMap(
                        course ->
                                course.topics()
                                        .stream()
                )
                .distinct()
                .sorted()
                .toList();

Read:

সব Course নাও
↓
published Course রাখো
↓
সব topics বের করো
↓
একটি flat Stream বানাও
↓
duplicate topics বাদ দাও
↓
sort করো
↓
List বানাও

This Is Where Streams Become Powerful

Traditional loops দিয়ে একই কাজ অবশ্যই possible।

কিন্তু আপনাকে manually manage করতে হতে পারে:

result collection
nested loop
duplicate handling
sorting

Stream pipeline operation sequence-এ intent compactভাবে প্রকাশ করতে পারে।


Equivalent Traditional Approach

Set<String> uniqueTopics =
        new HashSet<>();

for (
        Course course
        : courses
) {
    if (
            !course.published()
    ) {
        continue;
    }

    for (
            String topic
            : course.topics()
    ) {
        uniqueTopics.add(
                topic
        );
    }
}

List<String> topics =
        new ArrayList<>(
                uniqueTopics
        );

topics.sort(
        Comparator.naturalOrder()
);

এই approach-ও perfectly valid।


Which Version Is Better?

Stream version:

List<String> topics =
        courses.stream()
                .filter(
                        Course::published
                )
                .flatMap(
                        course ->
                                course.topics()
                                        .stream()
                )
                .distinct()
                .sorted()
                .toList();

যদি team Stream API comfortable হয়, intent খুব clearly পড়া যায়।

Traditional version explicit state দেখায় এবং debugging কিছু ক্ষেত্রে easier হতে পারে।

Context matters।


Finding Instead of Building a List

Suppose শুধু জানতে চাই:

কোনো published Course আছে কি?

Bad approach:

boolean exists =
        !courses.stream()
                .filter(
                        Course::published
                )
                .toList()
                .isEmpty();

এখানে unnecessary List তৈরি হচ্ছে।

Better:

boolean exists =
        courses.stream()
                .anyMatch(
                        Course::published
                );

Use the Operation That Matches the Question

Question:

Does any item match?

Use:

anyMatch()

Question:

Give me the first match.

Use:

findFirst()

Question:

Give me all matches.

Use:

filter(...).toList()

Avoid Collecting Unnecessarily

Bad:

int count =
        courses.stream()
                .filter(
                        Course::published
                )
                .toList()
                .size();

Better:

long count =
        courses.stream()
                .filter(
                        Course::published
                )
                .count();

We will cover aggregate operations more deeply in the next lesson।


Stream Pipeline and Type Reasoning

Consider:

courses.stream()
        .filter(
                Course::published
        )
        .flatMap(
                course ->
                        course.topics()
                                .stream()
        )
        .distinct()
        .sorted()
        .limit(
                5
        )
        .toList();

Type flow:

Stream<Course>
↓ filter
Stream<Course>
↓ flatMap
Stream<String>
↓ distinct
Stream<String>
↓ sorted
Stream<String>
↓ limit
Stream<String>
↓ toList
List<String>

এভাবে stage-by-stage type reason করলে complex pipelines সহজ হয়।


Common Mistake 1 — Confusing map() and flatMap()

If:

.map(
        Course::topics
)

result type:

Stream<List<String>>

If:

.flatMap(
        course ->
                course.topics()
                        .stream()
)

result:

Stream<String>

Common Mistake 2 — Returning Null from map() to Filter

Avoid:

.map(
        course ->
                course.published()
                ? course.title()
                : null
)

Better:

.filter(
        Course::published
)
.map(
        Course::title
)

Common Mistake 3 — Expecting sorted() to Mutate Source

It does not reorder the original Collection।

If you need source mutation:

list.sort(...)

Common Mistake 4 — Thinking limit(3) Makes Full Sort Cheap

This:

.sorted(...)
.limit(
        3
)

still requires sorting semantics।

For huge top-K workloads, PriorityQueue may be algorithmically better।


Common Mistake 5 — Wrong Operation Order

These are not equivalent:

filter(...)
.limit(...)

and:

limit(...)
.filter(...)

Always reason about pipeline sequence।


Common Mistake 6 — Building a List Just to Check Existence

Avoid:

filter(...)
.toList()
.isEmpty()

when:

anyMatch(...)

directly answers the question।


Common Mistake 7 — Using findFirst().get()

This is risky:

courses.stream()
        .filter(...)
        .findFirst()
        .get();

If no result exists:

NoSuchElementException

Use Optional APIs appropriately।

We will cover this later।


Common Mistake 8 — Assuming Stream Operations Are Free

distinct() may need tracking state।

sorted() can require substantial work and memory।

Stream syntax hides loop mechanics, but complexity still exists।


Common Mistake 9 — Huge Pipeline

A pipeline with:

15 transformations
nested Lambdas
side effects
complex conditions

may become harder to maintain than smaller named operations।

Extract meaningful methods or intermediate concepts when needed।


Common Mistake 10 — Stream for Everything

Some logic is naturally procedural।

Do not sacrifice clarity just to avoid writing a loop।


Practical Example — Course Catalog Query

Requirement:

Published courses থেকে
unique topics বের করতে হবে,
alphabetically sort করতে হবে,
প্রথম 5টি নিতে হবে।

Implementation:

List<String> topics =
        courses.stream()
                .filter(
                        Course::published
                )
                .flatMap(
                        course ->
                                course.topics()
                                        .stream()
                )
                .distinct()
                .sorted()
                .limit(
                        5
                )
                .toList();

Practical Example — Premium Course Titles

Requirement:

Published
price >= 500000
price descending
title return
List<String> premiumTitles =
        courses.stream()
                .filter(
                        Course::published
                )
                .filter(
                        course ->
                                course.priceInPaisa()
                                >= 500_000
                )
                .sorted(
                        Comparator.comparingLong(
                                Course::priceInPaisa
                        ).reversed()
                )
                .map(
                        Course::title
                )
                .toList();

Practical Example — Check Availability

At least one published free Course আছে কি?

boolean available =
        courses.stream()
                .anyMatch(
                        course ->
                                course.published()
                                && course.priceInPaisa()
                                == 0
                );

No List required।


Practical Example — Validate All Courses

সব Course-এর code nonblank কি?

boolean valid =
        courses.stream()
                .allMatch(
                        course ->
                                course.code()
                                        != null
                                && !course.code()
                                        .isBlank()
                );

Domain design-এ ideally constructor-এই invariant enforce করা উচিত।

তবুও allMatch() validation-style processing বুঝতে useful।


Practical Example — Find First Published Course

Optional<Course> course =
        courses.stream()
                .filter(
                        Course::published
                )
                .findFirst();

Practice 1 — map() or flatMap()

Each Course has:

List<String> topics

Need:

List<List<String>>

Use?

Answer

map(
        Course::topics
)

Practice 2

Need one:

List<String>

containing topics from every Course।

Answer

flatMap(
        course ->
                course.topics()
                        .stream()
)

Practice 3 — Unique Values

Remove duplicate topics।

Solution

.distinct()

Practice 4 — Sort Titles

List<String> titles =
        courses.stream()
                .map(
                        Course::title
                )
                .sorted()
                .toList();

What ordering?

Answer

String natural ordering।


Practice 5 — Sort by Price Descending

Solution

.sorted(
        Comparator.comparingLong(
                Course::priceInPaisa
        ).reversed()
)

Practice 6 — First 3 Values

Solution

.limit(
        3
)

Practice 7 — Skip First 10

Solution

.skip(
        10
)

Practice 8 — First Published Course

Solution

courses.stream()
        .filter(
                Course::published
        )
        .findFirst();

Practice 9 — At Least One Free Course

Solution

courses.stream()
        .anyMatch(
                course ->
                        course.priceInPaisa()
                        == 0
        );

Practice 10 — Every Course Published

Solution

courses.stream()
        .allMatch(
                Course::published
        );

Practice 11 — No Archived Course

Suppose:

CourseStatus.ARCHIVED

exists।

Solution

courses.stream()
        .noneMatch(
                course ->
                        course.status()
                        == CourseStatus.ARCHIVED
        );

Practice 12 — Operation Order

Given:

stream.filter(
        condition
).limit(
        5
)

What does it mean?

Answer

Condition match করা elements-এর মধ্যে প্রথম 5টি নেওয়া।


Practice 13

What about:

stream.limit(
        5
).filter(
        condition
)

Answer

Source-এর প্রথম 5টি element নেওয়া হবে, তারপর শুধু matching elements রাখা হবে।

Result count 5-এর কম হতে পারে।


Practice 14 — Source Mutation

Does this change courses order?

List<Course> sorted =
        courses.stream()
                .sorted(
                        comparator
                )
                .toList();

Answer

No।

Source Collection-এর order unchanged থাকে।


Practice 15 — Short-Circuit

Why can:

anyMatch(...)

be more efficient than:

filter(...)
.toList()

when we only need existence?

Answer

anyMatch() প্রথম matching element পাওয়া মাত্র answer determine করতে পারে এবং remaining elements process করার প্রয়োজন নাও হতে পারে।


True or False

  1. map() এবং flatMap() একই কাজ করে।
  2. map() nested collection তৈরি করতে পারে।
  3. flatMap() nested streams flatten করতে পারে।
  4. distinct() duplicate logical values remove করে।
  5. sorted() source List mutate করে।
  6. limit(5) maximum 5টি element রাখে।
  7. skip(5) প্রথম 5টি element বাদ দেয়।
  8. findFirst() একটি Optional return করতে পারে।
  9. anyMatch() full Stream সবসময় process করতেই হবে।
  10. allMatch() একটি failure পেলেই stop করতে পারে।
  11. Pipeline operation order result change করতে পারে।
  12. filter().limit() এবং limit().filter() সবসময় equivalent।
  13. sorted().limit(3) সবসময় top-3-এর optimal algorithm।
  14. Stream syntax algorithmic complexity eliminate করে না।
  15. anyMatch() existence check-এর জন্য natural operation।

Answers

1. False
2. True
3. True
4. True
5. False
6. True
7. True
8. True
9. False
10. True
11. True
12. False
13. False
14. True
15. True

Knowledge Check

Question 1

map() এবং flatMap()-এর মূল difference কী?

Question 2

distinct() কী করে?

Question 3

Stream sorted() এবং List.sort()-এর mutation behavior কীভাবে আলাদা?

Question 4

limit() কী করে?

Question 5

skip() কী করে?

Question 6

findFirst() কেন Optional return করে?

Question 7

anyMatch() কী প্রশ্নের উত্তর দেয়?

Question 8

allMatch() কী প্রশ্নের উত্তর দেয়?

Question 9

Short-circuiting বলতে কী বোঝায়?

Question 10

Pipeline operation order কেন গুরুত্বপূর্ণ?

Question 11

কেন sorted().limit(k) এবং bounded heap algorithmically equivalent নয়?

Question 12

কখন Stream pipeline ভেঙে named method extract করা উচিত?


Knowledge Check Answers

Answer 1

map() একটি element-কে একটি result value-তে transform করে।

Example:

Course
→ List<String>

তাহলে result হতে পারে:

Stream<List<String>>

flatMap() প্রতিটি element থেকে Stream তৈরি করে এবং nested Streams-কে একটি single Stream-এ flatten করে।

Example:

Course
→ Stream<String>

Final:

Stream<String>

Answer 2

distinct() logically duplicate elements বাদ দিয়ে unique elements রাখে।

Custom object-এর ক্ষেত্রে equality semantics গুরুত্বপূর্ণ।

Answer 3

list.sort(...)

existing List-এর order mutate করে।

কিন্তু:

list.stream()
        .sorted(...)
        .toList()

একটি sorted result তৈরি করে এবং source List reorder করে না।

Answer 4

limit(n) Stream থেকে maximum প্রথম n elements পর্যন্ত processing/result সীমিত করে।

Answer 5

skip(n) প্রথম n elements বাদ দিয়ে পরের elements process করে।

Answer 6

কারণ matching element নাও থাকতে পারে।

Optional presence বা absence explicitভাবে represent করে।

Answer 7

কমপক্ষে একটি element condition match করে কি?

Answer 8

সব elements condition match করে কি?

Answer 9

যখন result determine হয়ে গেলে Stream বাকি elements process না করেই stop করতে পারে, তাকে short-circuiting বলা হয়।

Examples:

findFirst()
anyMatch()
allMatch()

Answer 10

কারণ প্রতিটি operation previous stage-এর result-এর উপর কাজ করে।

Example:

filter(...)
.limit(...)

এবং:

limit(...)
.filter(...)

different semantics produce করতে পারে।

Answer 11

sorted() general sorting work করে, সাধারণভাবে O(n log n) scale-এর operation।

যদি শুধু k best elements দরকার এবং k অনেক ছোট হয়, bounded heap:

O(n log k)

approach more efficient হতে পারে।

Answer 12

যখন pipeline-এ long Lambdas, complex business rules, side effects, অথবা অনেক stages-এর কারণে intent বোঝা কঠিন হয়ে যায়, তখন meaningful named methods extract করা উচিত।


Practical Stream Decision Guide

Need only matching elements:

filter(...)

Need transform:

map(...)

Need flatten nested values:

flatMap(...)

Need unique values:

distinct()

Need ordered values:

sorted(...)

Need first n:

limit(n)

Need ignore first n:

skip(n)

Need first matching element:

findFirst()

Need know whether one matches:

anyMatch(...)

Need know whether all match:

allMatch(...)

Need know whether none match:

noneMatch(...)

Stream Transformation Mental Model

একটি complex pipeline এভাবে reason করুন:

আমার source type কী?

কোন values রাখতে হবে?

কোন values transform হবে?

একটি item থেকে অনেক item বের হচ্ছে কি?

Duplicate remove করতে হবে কি?

Order দরকার কি?

Result-এর subset দরকার কি?

আমি result collect করতে চাই,
নাকি শুধু existence জানতে চাই?

Example Full Pipeline

List<String> topics =
        courses.stream()
                .filter(
                        Course::published
                )
                .flatMap(
                        course ->
                                course.topics()
                                        .stream()
                )
                .distinct()
                .sorted()
                .limit(
                        10
                )
                .toList();

এই pipeline-এর story:

সব Course নাও

শুধু published Course রাখো

সব Course থেকে topics বের করো

nested topics flatten করো

duplicate বাদ দাও

alphabetical sort করো

প্রথম 10টি নাও

List বানাও

যখন Stream code এভাবে story-এর মতো পড়া যায়, তখন Stream API তার সবচেয়ে useful form-এ থাকে।


Lesson Summary

এই lesson-এ আমরা Stream transformation-এর আরও practical এবং powerful operations শিখেছি।

আমরা শিখেছি:

  • একাধিক filter() pipeline-এ chain করা যায়
  • filter() element রাখা বা বাদ দেওয়ার decision নেয়
  • map() element transform করে
  • Multiple map() দিয়ে transformation chain করা যায়
  • flatMap() nested Streams flatten করে
  • map() এবং flatMap() আলাদা purpose serve করে
  • distinct() duplicate values remove করে
  • Object equality distinct() behavior-এ গুরুত্বপূর্ণ
  • sorted() natural বা custom ordering support করে
  • Stream sorted() source collection mutate করে না
  • limit() result-এর প্রথম অংশ নেয়
  • skip() শুরু থেকে elements বাদ দেয়
  • skip() এবং limit() in-memory slicing করতে পারে
  • findFirst() প্রথম matching result খুঁজে
  • anyMatch() অন্তত একটি match আছে কি না দেখে
  • allMatch() সব values match করে কি না দেখে
  • noneMatch() কোনো value match করে না কি না দেখে
  • কিছু operations short-circuit করতে পারে
  • Pipeline operation order result change করতে পারে
  • Cheap filtering early করা useful হতে পারে যখন semantics allow করে
  • Stream syntax algorithmic complexity remove করে না
  • sorted().limit(k) সবসময় Top-K-এর optimal algorithm নয়
  • Existence check-এর জন্য unnecessary List build করার দরকার নেই
  • Stream pipeline বড় হলে named methods readability improve করতে পারে

সবচেয়ে গুরুত্বপূর্ণ distinction:

filter
→ কোন values থাকবে?

map
→ values কী হবে?

flatMap
→ nested values কীভাবে এক Stream হবে?

আর result query করার জন্য:

findFirst
→ প্রথমটি কোথায়?

anyMatch
→ অন্তত একটি আছে?

allMatch
→ সবগুলো ঠিক?

noneMatch
→ একটিও নেই?

Next Lesson

পরবর্তী lesson:

Collectors, Grouping, and Reduction

আমরা শিখব:

  • Stream result aggregate করা
  • collect()
  • Collectors
  • Collectors.toList()
  • Collectors.toSet()
  • joining()
  • groupingBy()
  • partitioningBy()
  • toMap()
  • Duplicate key handling
  • counting()
  • mapping()
  • reduce()
  • Sum, minimum, maximum-style reduction
  • Mutable collection vs reduction
  • কখন collect() এবং কখন reduce() ব্যবহার করা উচিত