Modern Java
Filtering, Mapping, and Transforming Streams
আপনি একটি 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
map()এবংflatMap()একই কাজ করে।map()nested collection তৈরি করতে পারে।flatMap()nested streams flatten করতে পারে।distinct()duplicate logical values remove করে।sorted()source List mutate করে।limit(5)maximum 5টি element রাখে।skip(5)প্রথম 5টি element বাদ দেয়।findFirst()একটিOptionalreturn করতে পারে।anyMatch()full Stream সবসময় process করতেই হবে।allMatch()একটি failure পেলেই stop করতে পারে।- Pipeline operation order result change করতে পারে।
filter().limit()এবংlimit().filter()সবসময় equivalent।sorted().limit(3)সবসময় top-3-এর optimal algorithm।- Stream syntax algorithmic complexity eliminate করে না।
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()CollectorsCollectors.toList()Collectors.toSet()joining()groupingBy()partitioningBy()toMap()- Duplicate key handling
counting()mapping()reduce()- Sum, minimum, maximum-style reduction
- Mutable collection vs reduction
- কখন
collect()এবং কখনreduce()ব্যবহার করা উচিত