Modern Java

Introduction to the Stream API

ReadingPreview

You are viewing a free preview lesson.

Lesson Overview

Java-তে Collection নিয়ে কাজ করার traditional approach আমরা ইতিমধ্যে দেখেছি।

Example:

List<Course> courses

ধরুন আমরা চাই:

শুধু published Course নিতে
তারপর title বের করতে
তারপর result List বানাতে

Traditional loop দিয়ে:

List<String> titles =
        new ArrayList<>();

for (
        Course course
        : courses
) {
    if (
            course.published()
    ) {
        titles.add(
                course.title()
        );
    }
}

এই code valid এবং পরিষ্কার।

Modern Java-তে একই কাজ Stream API ব্যবহার করে এভাবে লেখা যায়:

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

এখানে আমরা step-by-step বলছি:

courses নাও
↓
published Course রাখো
↓
title-এ transform করো
↓
List বানাও

এটাই Stream-এর core idea:

Data processing pipeline

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

  • Stream কী
  • Collection এবং Stream-এর difference
  • Stream pipeline
  • Stream source
  • Intermediate operations
  • Terminal operations
  • stream()
  • filter()
  • map()
  • forEach()
  • toList()
  • Lazy evaluation
  • কেন Stream একবার consume করা যায়
  • Stream কেন data store করে না
  • Loop বনাম Stream
  • কখন Stream clearer
  • কখন traditional loop better

What Is a Stream?

Stream হলো data elements-এর উপর একটি processing pipeline।

Stream নিজে data structure নয়।

এটি:

ArrayList
HashSet
array

এর মতো data store করার container নয়।

বরং Stream কোনো source থেকে elements নিয়ে তাদের উপর operations apply করে।

Conceptually:

Source
↓
Operation
↓
Operation
↓
Result

Simple Example

Suppose:

List<Integer> numbers =
        List.of(
                10,
                20,
                30,
                40
        );

Create Stream:

numbers.stream()

এই Stream List-এর elements process করতে পারবে।

কিন্তু:

numbers.stream();

মাত্র লিখলে কোনো useful result পাওয়া যায় না।

আমাদের operations দিতে হবে।


Stream Pipeline

একটি Stream pipeline সাধারণত তিনটি অংশ নিয়ে গঠিত:

1. Source
2. Intermediate Operations
3. Terminal Operation

Example:

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

Breakdown:

Source:
courses.stream()

Intermediate:
filter(...)
map(...)

Terminal:
toList()

Source

Stream-এর data কোথা থেকে আসছে সেটি হলো source।

Example:

courses.stream()

এখানে source:

courses

Another Source Example

List<String> names =
        List.of(
                "Sakib",
                "Subu",
                "Sumu"
        );

Stream<String> stream =
        names.stream();

এখানে:

List<String>

থেকে:

Stream<String>

create হয়েছে।


Collection vs Stream

এটি খুব গুরুত্বপূর্ণ distinction।

Collection

Collection:

data store করে

Example:

List<Course>
Set<String>

Collection-এর lifetime থাকতে পারে।

আমরা repeatedly access করতে পারি।


Stream

Stream:

data process করে

Stream সাধারণত source-এর উপর একটি processing view/pipeline।

Stream elements permanently own করে না।


Mental Model

Think:

Collection
→ data কোথায় আছে?

Stream
→ data নিয়ে কী processing হবে?

stream()

Collection থেকে Stream পেতে:

collection.stream()

Example:

Stream<Course> courseStream =
        courses.stream();

Import:

import java.util.stream.Stream;

কিন্তু অনেক সময় Stream variable create না করে directly pipeline লিখি।

courses.stream()
        .filter(...)
        .map(...)
        .toList();

First Stream Example

Suppose:

List<Integer> numbers =
        List.of(
                10,
                20,
                30,
                40,
                50
        );

আমরা শুধু 30-এর বেশি numbers চাই।

List<Integer> result =
        numbers.stream()
                .filter(
                        number ->
                                number > 30
                )
                .toList();

Result:

40
50

filter()

filter() decide করে কোন elements pipeline-এ থাকবে।

এটি একটি:

Predicate<T>

নেয়।

Recall:

Predicate<T>
→ T → boolean

Example:

number ->
        number > 30

যেখানে result:

true

সেই element রাখা হয়।

যেখানে:

false

সেই element বাদ যায়।


Filter Mental Model

Input elements
↓
condition check
↓
keep only matching elements

Example:

10 → false → remove
20 → false → remove
30 → false → remove
40 → true  → keep
50 → true  → keep

Course Filter Example

record Course(
        String title,
        long priceInPaisa,
        boolean published
) {
}

Published courses:

List<Course> published =
        courses.stream()
                .filter(
                        Course::published
                )
                .toList();

Equivalent Lambda

.filter(
        course ->
                course.published()
)

Method reference:

.filter(
        Course::published
)

দুইটিই same behavior express করে।


Multiple Filters

Suppose published এবং paid Course চাই।

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

Pipeline read করা যায়:

সব Course
↓
published রাখো
↓
paid রাখো
↓
List বানাও

One Combined Filter

এটিও possible:

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

কোনটি clearer হবে context-এর উপর depend করে।


Named Predicate

Reusable rules হলে:

Predicate<Course> published =
        Course::published;

Predicate<Course> paid =
        course ->
                course.priceInPaisa()
                > 0;

Then:

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

map()

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

এটি একটি:

Function<T, R>

নেয়।

Recall:

Function<T, R>
→ T → R

Example

Suppose:

List<String> names =
        List.of(
                "Sakib",
                "Subu",
                "Sumu"
        );

Lengths:

List<Integer> lengths =
        names.stream()
                .map(
                        String::length
                )
                .toList();

Result:

5
4
4

What map() Means

Input:

String

Output:

Integer

So Stream type changes:

Stream<String>
↓ map(String::length)
Stream<Integer>

Course to Title

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

Input:

Course

Output:

String

Course to Price

List<Long> prices =
        courses.stream()
                .map(
                        Course::priceInPaisa
                )
                .toList();

Filter and Map Together

Suppose published Course titles চাই।

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

Read:

Course list নাও
↓
published Course রাখো
↓
প্রতিটি Course থেকে title বের করো
↓
List<String> বানাও

This Is a Pipeline

Stream code line-by-line mutation-এর বদলে transformation stages express করে।

Traditional:

List<String> result =
        new ArrayList<>();

for (
        Course course
        : courses
) {
    if (
            course.published()
    ) {
        result.add(
                course.title()
        );
    }
}

Stream:

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

দুইটিই valid।

Stream version processing intent declaratively express করছে।


Imperative vs Declarative Style

Traditional loop অনেক সময় বলে:

কীভাবে step-by-step কাজ করতে হবে

Example:

result list বানাও
loop করো
condition check করো
add করো

Stream pipeline বেশি বলে:

আমি কী result চাই

Example:

published courses
→ titles
→ list

এটিকে broadly:

declarative style

বলা যায়।


Streams Are Not Automatically Better

একটি important rule:

Stream ব্যবহার করা মানেই code better নয়।

Simple বা complex mutable control flow-এর ক্ষেত্রে loop clearer হতে পারে।

Goal:

readability
correctness
maintainability

not:

use Stream everywhere

toList()

Modern Java Stream-এ:

.toList()

Stream elements collect করে একটি List return করে।

Example:

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

Important: Stream.toList() Result

Stream.toList() এমন একটি List return করে যেটিকে আপনি mutable ArrayList হিসেবে assume করবেন না।

যদি result modify করার প্রয়োজন থাকে, explicit mutable copy create করুন।

Example:

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

Now titles is mutable because we created an ArrayList copy।


forEach()

forEach() একটি terminal operation।

Example:

courses.stream()
        .forEach(
                course ->
                        System.out.println(
                                course.title()
                        )
        );

Do We Need stream() for Simple forEach()?

Not necessarily।

This:

courses.forEach(
        course ->
                System.out.println(
                        course.title()
                )
);

is simpler than:

courses.stream()
        .forEach(...)

যদি কোনো intermediate Stream operation না থাকে।


Useful forEach() Pipeline

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

এখানে filter করার পরে side effect করা হচ্ছে।


Intermediate Operations

filter() এবং map() হলো:

Intermediate Operations

Intermediate operation:

another Stream return করে

Example:

Stream<Course> published =
        courses.stream()
                .filter(
                        Course::published
                );

Common Intermediate Operations

আমরা পরে আরও শিখব:

filter()
map()
flatMap()
distinct()
sorted()
limit()
skip()

এগুলো pipeline build করে।


Terminal Operations

Terminal operation Stream processing trigger করে এবং pipeline শেষ করে।

Examples:

toList()
forEach()
count()
findFirst()
anyMatch()
reduce()
collect()

এগুলোর অনেকগুলো আমরা পরের lessons-এ শিখব।


Example

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

Here:

stream()
→ source

filter()
→ intermediate

map()
→ intermediate

toList()
→ terminal

Why Is Terminal Operation Important?

Consider:

courses.stream()
        .filter(
                course -> {
                    System.out.println(
                            "Checking "
                            + course.title()
                    );

                    return course.published();
                }
        );

There is no terminal operation।

Normally pipeline-এর processing execute করার প্রয়োজন এখনো তৈরি হয়নি।

Stream intermediate operations সাধারণত lazy।


Lazy Evaluation

Lazy Evaluation মানে:

Stream pipeline-এর intermediate operations
প্রয়োজন হওয়ার আগে actual processing শুরু করে না।

Terminal operation actual result demand করলে processing ঘটে।


Example

Stream<Course> stream =
        courses.stream()
                .filter(
                        course -> {
                            System.out.println(
                                    "Filtering "
                                    + course.title()
                            );

                            return course.published();
                        }
                );

এখানে Stream pipeline তৈরি হয়েছে।

কিন্তু terminal operation এখনো নেই।


Add Terminal Operation

List<Course> result =
        stream.toList();

এখন Stream values process করবে।


Why Laziness Matters

Laziness Java-কে কিছু pipelines efficiently execute করতে সাহায্য করে।

Example:

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

যদি প্রথম matching element পাওয়া যায়, পুরো source process করার প্রয়োজন নাও হতে পারে।

এই behavior আমরা later lesson-এ বিস্তারিত দেখব।


Pipeline Is Often Element-by-Element

Beginners কখনো কখনো ভাবেন:

প্রথমে পুরো List filter হবে
তারপর পুরো result map হবে

Conceptually বুঝতে এই model useful হতে পারে।

কিন্তু Stream implementation অনেক ক্ষেত্রে elements pipeline-এর stages দিয়ে process করতে পারে।

Example conceptual flow:

Course 1
→ filter
→ map

Course 2
→ filter
→ maybe stop

Course 3
→ filter
→ map

এ কারণেও laziness useful।


Stream Can Be Consumed Once

একটি Stream pipeline সাধারণত একবার terminal operation-এর মাধ্যমে consume করা যায়।

Example:

Stream<Course> stream =
        courses.stream();

List<Course> first =
        stream.toList();

এরপর একই Stream আবার:

stream.toList();

reuse করা উচিত নয়।

Runtime-এ exception হতে পারে কারণ Stream already operated upon or closed।


Create a New Stream Instead

Collection reusable।

So:

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

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

প্রতিবার নতুন Stream create করুন।


Collection Is Reusable

courses

কে repeatedly iterate করা যায়।

Stream:

courses.stream()

একটি particular processing pipeline instance।

Mental model:

Collection
→ reusable data container

Stream
→ one processing journey

Do Not Store Streams as Long-Lived State

Usually avoid design like:

class CourseService {

    private Stream<Course> courses;
}

A Stream সাধারণত long-lived state হিসেবে suitable নয়।

Store:

List<Course>

or another collection।

তারপর প্রয়োজন হলে:

courses.stream()

create করুন।


Streams Do Not Mutate Source by Default

Suppose:

List<Integer> numbers =
        new ArrayList<>(
                List.of(
                        10,
                        20,
                        30
                )
        );

Then:

List<Integer> doubled =
        numbers.stream()
                .map(
                        number ->
                                number * 2
                )
                .toList();

numbers remains:

10
20
30

doubled:

20
40
60

But Object Mutation Is Still Possible

This is possible:

courses.stream()
        .forEach(
                course ->
                        course.changeSomething()
        );

যদি objects mutable হয়।

Stream নিজে automatic immutability দেয় না।


Prefer Transformations Over Hidden Mutation

Clear Stream pipelines generally focus on:

filter
transform
collect

rather than hiding lots of external mutation inside Lambdas।

Bad style example:

List<String> result =
        new ArrayList<>();

courses.stream()
        .filter(...)
        .forEach(
                course ->
                        result.add(
                                course.title()
                        )
        );

This works, but:

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

better expresses the transformation।


Filter vs Map

এই difference খুব গুরুত্বপূর্ণ।

filter()

Question:

এই element থাকবে কি?

Input এবং output element type same থাকে।

Example:

Course
→ Course

কিছু elements বাদ যায়।


map()

Question:

এই element কী value-তে transform হবে?

Example:

Course
→ String

Example

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

filter():

Course রাখবে বা বাদ দেবে

map():

Course-কে title String-এ transform করবে

Do Not Use map() as a Filter

Bad thinking:

map-এর ভিতরে null return করব
যেগুলো দরকার নেই।

Example:

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

Then result may contain nulls।

Better:

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

Each operation should express its purpose।


Do Not Use filter() for Transformation

filter() predicate returns boolean।

It should answer:

keep or remove?

Not:

transform into something else

Pipeline Order Matters

Consider:

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

After map():

Stream<String>

So this:

.filter(
        Course::published
)

cannot come afterward because elements are no longer Course


Type Changes Through Pipeline

Start:

Stream<Course>

After:

.filter(
        Course::published
)

still:

Stream<Course>

After:

.map(
        Course::title
)

becomes:

Stream<String>

Understanding this type flow makes Stream code much easier।


Another Type Flow Example

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

Type flow:

Stream<Course>
↓
Stream<String>
↓
Stream<Integer>
↓
List<Integer>

Filter After Map

This is also valid if the condition now applies to mapped values।

Example:

List<String> longTitles =
        courses.stream()
                .map(
                        Course::title
                )
                .filter(
                        title ->
                                title.length()
                                > 10
                )
                .toList();

Here:

Course → title

then:

filter titles

Could Filter Before Map Be Better?

If condition depends on Course:

courses.stream()
        .filter(
                course ->
                        course.title()
                                .length()
                        > 10
        )
        .map(
                Course::title
        )

This can sometimes express the intent more directly।

Pipeline order should follow logical processing needs।


Example — Paid Course Titles

List<String> paidTitles =
        courses.stream()
                .filter(
                        course ->
                                course.priceInPaisa()
                                > 0
                )
                .map(
                        Course::title
                )
                .toList();

Equivalent Loop

List<String> paidTitles =
        new ArrayList<>();

for (
        Course course
        : courses
) {
    if (
            course.priceInPaisa()
            > 0
    ) {
        paidTitles.add(
                course.title()
        );
    }
}

Both correct।


Which One Should You Use?

If pipeline naturally reads as:

filter
transform
collect

Stream often works well।

If algorithm needs:

complex branching
many state changes
multiple early exits
index manipulation

loop may be clearer।


Example Where Loop Is Better

Suppose:

Process courses one by one
stop immediately after third invalid course
print detailed reason for each
update multiple counters

Trying to force everything into one Stream pipeline may reduce readability।

Traditional loop:

for (...)

can communicate the state machine more clearly।


Do Not Use Streams to Look Modern

Bad motivation:

Stream code looks advanced.

Good motivation:

Stream pipeline expresses the transformation clearly.

Stream Pipelines Should Read Like a Story

Good:

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

Read:

সব courses থেকে
published courses রাখো
title বের করো
list বানাও

Avoid Giant Lambdas Inside Streams

Less readable:

courses.stream()
        .filter(
                course -> {
                    // 20 lines
                }
        )
        .map(
                course -> {
                    // another 15 lines
                }
        )
        .toList();

Extract meaningful methods:

courses.stream()
        .filter(
                this::isEligible
        )
        .map(
                this::toSummary
        )
        .toList();

যখন method names domain intent explain করে।


Stream and Side Effects

Side effects possible:

courses.stream()
        .forEach(
                course ->
                        System.out.println(
                                course.title()
                        )
        );

Printing intentionally side-effecting।

কিন্তু transformation pipeline-এর ভিতরে hidden mutation avoid করা ভালো।


Example of Unclear Side Effect

int[] counter = {
        0
};

courses.stream()
        .filter(
                course -> {
                    counter[0]++;

                    return course.published();
                }
        )
        .toList();

Technically possible হলেও:

filter

এখন condition check-এর পাশাপাশি external state mutate করছে।

এটি reasoning harder করে।


Better Separation

If count দরকার:

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

count() আমরা later lesson-এ বিস্তারিত দেখব।


Stream Is Not a Database Query

Stream syntax কিছুটা query-like মনে হতে পারে।

কিন্তু:

courses.stream()

Java memory-তে existing objects-এর উপর processing করছে।

এটি automatically:

SQL query
database filtering
remote processing

নয়।

Frameworks কখনো similar-looking APIs provide করতে পারে, কিন্তু Java Stream-এর semantics আলাদা।


Stream Is Not Parallel by Default

Normal:

courses.stream()

sequential Stream।

এটি automatically multiple threads ব্যবহার করে না।

Parallel streams exist:

parallelStream()

কিন্তু parallelism-এর tradeoffs আছে এবং blindly ব্যবহার করা উচিত নয়।

এই foundation lesson-এ আমরা sequential Streams নিয়ে কাজ করব।


Stream Does Not Guarantee Faster Code

Stream syntax concise হতে পারে।

কিন্তু:

Stream
≠ automatically faster

Traditional loop অনেক ক্ষেত্রে equally fast বা simpler হতে পারে।

Performance-sensitive situation-এ:

measure
profile
understand allocations

important।


Practical Example — Published Course Titles

import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<Course> courses =
                List.of(
                        new Course(
                                "Java Foundation",
                                true
                        ),
                        new Course(
                                "Backend Development",
                                false
                        ),
                        new Course(
                                "System Design",
                                true
                        )
                );

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

        System.out.println(
                titles
        );
    }

    record Course(
            String title,
            boolean published
    ) {
    }
}

Result:

[Java Foundation, System Design]

Practical Example — Transform Numbers

import java.util.List;

public class Main {

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

        List<Integer> doubledEvenNumbers =
                numbers.stream()
                        .filter(
                                number ->
                                        number % 2
                                        == 0
                        )
                        .map(
                                number ->
                                        number * 2
                        )
                        .toList();

        System.out.println(
                doubledEvenNumbers
        );
    }
}

Result:

[4, 8]

Pipeline Trace

Input:

1 2 3 4 5

Filter even:

2 4

Map × 2:

4 8

Collect:

[4, 8]

Practice 1 — Identify Source

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

Source কী?

Answer

courses

Practice 2 — Identify Intermediate Operation

Which is intermediate?

stream()
filter()
toList()

Answer

filter()

stream() source Stream create করছে।

toList() terminal operation।


Practice 3 — Identify Terminal Operation

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

Answer

toList()

Practice 4 — Filter

Given:

List<Integer> numbers

শুধু positive values রাখুন।

Solution

List<Integer> positive =
        numbers.stream()
                .filter(
                        number ->
                                number > 0
                )
                .toList();

Practice 5 — Map

Strings-এর lengths বের করুন।

Solution

List<Integer> lengths =
        names.stream()
                .map(
                        String::length
                )
                .toList();

Practice 6 — Filter and Map

Published Course-গুলোর title বের করুন।

Solution

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

Practice 7 — Type Flow

Start:

Stream<Course>

After:

.map(
        Course::title
)

type কী?

Answer

Stream<String>

Practice 8 — Lazy Evaluation

এই code কি terminal operation ছাড়াই full filtering result create করবে?

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

Answer

সাধারণভাবে না।

filter() intermediate এবং lazy operation।

Actual processing terminal operation প্রয়োজন হলে execute হয়।


Practice 9 — Reuse

Valid design?

Stream<Course> stream =
        courses.stream();

stream.toList();

stream.toList();

Answer

No।

একটি Stream একবার terminal operation দিয়ে consume হওয়ার পরে reuse করা উচিত নয়।

নতুন Stream create করুন।


Practice 10 — Collection vs Stream

কোনটি data store করে?

List
Stream

Answer

List

Stream মূলত processing pipeline।


Practice 11 — Mutation

Does this mutate numbers?

List<Integer> doubled =
        numbers.stream()
                .map(
                        number ->
                                number * 2
                )
                .toList();

Answer

No।

এটি mapped values নিয়ে নতুন result List তৈরি করে।

Original numbers unchanged থাকে।


Practice 12 — Choose Loop or Stream

Requirement:

Published courses থেকে title বের করে List বানাতে হবে।

Stream suitable?

Answer

Yes।

এটি natural:

filter
→ map
→ toList

pipeline।


Practice 13 — Choose Loop or Stream

Requirement:

Loop-এর মাঝখানে multiple counters update করতে হবে,
specific condition-এ break করতে হবে,
index track করতে হবে।

Answer

Traditional loop likely clearer।


Practice 14 — forEach()

Print every published course title।

Solution

courses.stream()
        .filter(
                Course::published
        )
        .map(
                Course::title
        )
        .forEach(
                System.out::println
        );

True or False

  1. Stream একটি data structure।
  2. Collection data store করে।
  3. Stream data processing pipeline represent করে।
  4. filter() একটি intermediate operation।
  5. map() transformation করে।
  6. toList() একটি terminal operation।
  7. Stream intermediate operations সাধারণত lazy।
  8. Stream instance repeatedly consume করা যায়।
  9. Collection থেকে নতুন Stream repeatedly তৈরি করা যায়।
  10. filter() element type necessarily change করে।
  11. map() element type change করতে পারে।
  12. Stream ব্যবহার করলেই code automatically faster হয়।
  13. Stream ব্যবহার করলেই code automatically clearer হয়।
  14. Traditional loops modern Java-তে obsolete।
  15. Stream pipeline side effects করতে পারে, কিন্তু hidden mutation carefully use করা উচিত।

Answers

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

Knowledge Check

Question 1

Stream কী?

Question 2

Collection এবং Stream-এর মূল difference কী?

Question 3

একটি Stream pipeline-এর তিনটি প্রধান অংশ কী?

Question 4

Intermediate operation কী?

Question 5

Terminal operation কী?

Question 6

filter() কী করে?

Question 7

map() কী করে?

Question 8

Lazy evaluation বলতে কী বোঝায়?

Question 9

কেন একই Stream সাধারণত দুইবার consume করা যায় না?

Question 10

Stream<Course> কীভাবে Stream<String> হতে পারে?

Question 11

Stream কখন traditional loop-এর চেয়ে clearer হতে পারে?

Question 12

Traditional loop কখন Stream-এর চেয়ে better হতে পারে?


Knowledge Check Answers

Answer 1

Stream হলো source data-এর elements-এর উপর operations apply করার একটি processing pipeline।

এটি List বা Set-এর মতো data storage container নয়।

Answer 2

Collection মূলত data store করে।

Stream সেই data-এর উপর processing describe এবং execute করে।

Answer 3

Source

Intermediate Operations

Terminal Operation

Answer 4

Intermediate operation Stream-এর উপর transformation বা filtering stage add করে এবং আরেকটি Stream return করে।

Examples:

filter()
map()

Answer 5

Terminal operation Stream pipeline-এর final result বা side effect produce করে এবং pipeline consume করে।

Examples:

toList()
forEach()

Answer 6

filter() প্রতিটি element-এর condition evaluate করে এবং যেগুলোর Predicate true হয় সেগুলো রাখে।

Answer 7

map() প্রতিটি input element-কে একটি output value-তে transform করে।

Example:

Course
→ title String

Answer 8

Intermediate Stream operations সাধারণত actual processing delay করে যতক্ষণ না terminal operation result demand করে।

Answer 9

একটি Stream একটি single processing pipeline represent করে। Terminal operation-এর পরে সেই pipeline consumed হয়ে যায়।

একই source থেকে প্রয়োজন হলে নতুন Stream তৈরি করতে হয়।

Answer 10

map() ব্যবহার করে।

Example:

courses.stream()
        .map(
                Course::title
        )

এখানে:

Course
→ String

transformation হচ্ছে।

Answer 11

যখন requirement naturally:

filter
transform
collect

ধরনের pipeline হিসেবে পড়া যায়।

Answer 12

যখন complex control flow, break, continue, indexes, multiple mutable variables বা detailed procedural state দরকার হয়।


Practical Stream Checklist

Stream লিখতে গেলে জিজ্ঞেস করুন:

Source কী?

প্রতিটি element-এর কোনগুলো রাখতে চাই?

কোন transformation দরকার?

Pipeline-এর type প্রতিটি stage-এ কী?

Terminal operation কী?

Stream reuse করার চেষ্টা করছি কি?

External state mutate করছি কি?

এই Stream pipeline traditional loop-এর চেয়ে সত্যিই clearer কি?

Stream Pipeline Cheat Sheet

Create Stream

values.stream()

Filter Values

values.stream()
        .filter(
                value ->
                        condition
        )

Transform Values

values.stream()
        .map(
                value ->
                        transformedValue
        )

Convert to List

values.stream()
        .toList()

Filter + Transform + List

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

Process Each Result

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

Core Mental Model

Stream বুঝতে সবচেয়ে useful mental model:

Source
↓
Keep what I need
↓
Transform what remains
↓
Produce a result

Example:

All Courses
↓
Published Courses
↓
Course Titles
↓
List<String>

Java code:

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

Lesson Summary

এই lesson-এ আমরা Java Stream API-এর foundation শিখেছি।

আমরা শিখেছি:

  • Stream একটি processing pipeline
  • Stream data structure নয়
  • Collection data store করে, Stream data process করে
  • Collection থেকে stream() দিয়ে Stream তৈরি করা যায়
  • Stream pipeline-এর source, intermediate operations এবং terminal operation থাকে
  • filter() matching elements রাখে
  • map() elements transform করে
  • toList() result List তৈরি করে
  • forEach() side-effecting terminal processing করতে পারে
  • Intermediate operations সাধারণত lazy
  • Terminal operation processing trigger করে
  • একটি Stream সাধারণত একবার consume করা যায়
  • একই Collection থেকে প্রয়োজন অনুযায়ী নতুন Streams তৈরি করা যায়
  • filter() সাধারণত element type পরিবর্তন করে না
  • map() Stream-এর element type পরিবর্তন করতে পারে
  • Stream source automatically mutate হয় না
  • Object mutation তারপরও possible
  • Hidden external mutation Stream pipeline-কে harder to reason about করতে পারে
  • Stream automatically faster নয়
  • Stream automatically better নয়
  • Simple filter → map → collect transformations-এ Stream খুব readable হতে পারে
  • Complex procedural control flow-এর ক্ষেত্রে traditional loop clearer হতে পারে

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

Collection
→ data

Stream
→ data processing

আর সবচেয়ে important Stream pattern:

Source
→ Filter
→ Transform
→ Result

Next Lesson

পরবর্তী lesson:

Filtering, Mapping, and Transforming Streams

আমরা আরও deeply শিখব:

  • Multiple filter() operations
  • map()
  • Chained transformations
  • flatMap()
  • Nested collections flatten করা
  • distinct()
  • sorted()
  • limit()
  • skip()
  • findFirst()
  • anyMatch()
  • allMatch()
  • Stream pipeline ordering
  • Short-circuiting behavior
  • Practical transformation patterns