Modern Java
Introduction to the Stream API
আপনি একটি 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
- Stream একটি data structure।
- Collection data store করে।
- Stream data processing pipeline represent করে।
filter()একটি intermediate operation।map()transformation করে।toList()একটি terminal operation।- Stream intermediate operations সাধারণত lazy।
- Stream instance repeatedly consume করা যায়।
- Collection থেকে নতুন Stream repeatedly তৈরি করা যায়।
filter()element type necessarily change করে।map()element type change করতে পারে।- Stream ব্যবহার করলেই code automatically faster হয়।
- Stream ব্যবহার করলেই code automatically clearer হয়।
- Traditional loops modern Java-তে obsolete।
- 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 → collecttransformations-এ 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