Modern Java
Collectors, Grouping, and Reduction
আপনি একটি free preview lesson দেখছেন।
Lesson Overview
আগের lessons-এ আমরা Stream দিয়ে data:
filter করেছি
transform করেছি
flatten করেছি
sort করেছি
match করেছি
Example:
courses.stream()
.filter(
Course::published
)
.map(
Course::title
)
.toList();
এখানে final result ছিল:
List<String>
কিন্তু বাস্তব application-এ সবসময় List দরকার হয় না।
আমাদের প্রয়োজন হতে পারে:
unique values-এর Set
key-value Map
category অনুযায়ী grouping
true/false অনুযায়ী partition
comma-separated String
frequency count
total amount
একটি combined result
এই ধরনের result তৈরির জন্য Stream API-তে গুরুত্বপূর্ণ দুইটি concept হলো:
collect()
reduce()
এবং collect()-এর সাথে commonly ব্যবহার হয়:
Collectors
এই lesson-এ আমরা শিখব:
collect()কীCollectorconceptCollectors.toList()Collectors.toSet()joining()toMap()- Duplicate key handling
groupingBy()- Downstream collectors
counting()mapping()partitioningBy()reduce()- Identity value
- Accumulator
- Sum এবং product reduction
- Object reduction
collect()বনামreduce()- Common mistakes
Why Do We Need Collectors?
Suppose:
List<Course> courses
থেকে published Course-এর titles চাই।
আমরা already জানি:
List<String> titles =
courses.stream()
.filter(
Course::published
)
.map(
Course::title
)
.toList();
কিন্তু যদি চাই:
unique titles
তাহলে Set দরকার।
যদি চাই:
Course code → Course
তাহলে Map দরকার।
যদি চাই:
status → courses
তাহলে grouping দরকার।
অর্থাৎ:
Stream elements
→ কোনো structured result
এই transformation-এর জন্য collect() খুব powerful।
What Is collect()?
collect() হলো একটি terminal Stream operation।
এটি Stream-এর elements নিয়ে একটি result container বা structured result তৈরি করতে পারে।
Conceptually:
Stream elements
↓
Collector
↓
Result
Example:
List<String> titles =
courses.stream()
.map(
Course::title
)
.collect(
Collectors.toList()
);
collect() Is a Terminal Operation
এই pipeline:
courses.stream()
.map(
Course::title
)
.collect(
Collectors.toList()
);
এখানে:
stream()
→ source
map()
→ intermediate
collect()
→ terminal
collect() pipeline consume করে result তৈরি করে।
Collectors
Java utility class:
java.util.stream.Collectors
অনেক predefined collectors দেয়।
Import:
import java.util.stream.Collectors;
Common collectors:
Collectors.toList()
Collectors.toSet()
Collectors.toMap()
Collectors.joining()
Collectors.groupingBy()
Collectors.partitioningBy()
Collectors.counting()
Collectors.mapping()
Collectors.toList()
Example:
List<String> titles =
courses.stream()
.map(
Course::title
)
.collect(
Collectors.toList()
);
এটি Stream elements একটি List-এ collect করে।
toList() vs Collectors.toList()
Modern Java-তে আমরা সরাসরি লিখতে পারি:
stream.toList();
তাই simple List result-এর জন্য:
courses.stream()
.map(
Course::title
)
.toList();
সাধারণত cleaner।
তবে Collectors.toList() জানা important কারণ:
Collectors API-এর অন্যান্য collectors-এর সাথে একই mental model ব্যবহার হয়
এবং older/common Java codebases-এ এটিও frequently দেখা যায়।
Do Not Assume Exact List Implementation
এই code:
.collect(
Collectors.toList()
)
থেকে result কোন exact List implementation হবে তা application code-এর assumption হওয়া উচিত নয়।
যদি explicitly mutable ArrayList দরকার হয়, পরিষ্কারভাবে create করুন।
Example:
List<String> titles =
courses.stream()
.map(
Course::title
)
.collect(
Collectors.toCollection(
ArrayList::new
)
);
Collectors.toSet()
Unique values দরকার হলে:
Set<String> topics =
courses.stream()
.flatMap(
course ->
course.topics()
.stream()
)
.collect(
Collectors.toSet()
);
Result:
Set<String>
Duplicate topics থাকবে না।
distinct().toList() vs toSet()
দুইটির intent আলাদা।
If requirement:
unique values as a List
use:
stream.distinct()
.toList();
If requirement:
result itself should be a Set
use:
.collect(
Collectors.toSet()
);
Set Ordering
Collectors.toSet() থেকে কোনো specific iteration order assume করা উচিত নয়।
যদি insertion order specifically দরকার হয়:
.collect(
Collectors.toCollection(
LinkedHashSet::new
)
);
যদি sorted Set দরকার হয়:
.collect(
Collectors.toCollection(
TreeSet::new
)
);
joining()
Suppose titles:
Java Foundation
Backend Development
System Design
আমরা চাই:
Java Foundation, Backend Development, System Design
Use:
String titles =
courses.stream()
.map(
Course::title
)
.collect(
Collectors.joining(
", "
)
);
joining() Mental Model
String elements
↓
join using delimiter
↓
one String
Prefix and Suffix
joining() prefix এবং suffix-ও নিতে পারে।
String titles =
courses.stream()
.map(
Course::title
)
.collect(
Collectors.joining(
", ",
"[",
"]"
)
);
Result:
[Java Foundation, Backend Development, System Design]
toMap()
Suppose আমাদের দরকার:
Course code → Course
Use:
Map<String, Course> byCode =
courses.stream()
.collect(
Collectors.toMap(
Course::code,
course ->
course
)
);
Key Mapper and Value Mapper
toMap() এখানে দুইটি behavior নিচ্ছে।
Course::code
defines:
Course
→ key
And:
course -> course
defines:
Course
→ value
Function.identity()
এই Lambda:
course ->
course
means:
input যেটা
output সেটাই
Java already provides:
Function.identity()
So:
Map<String, Course> byCode =
courses.stream()
.collect(
Collectors.toMap(
Course::code,
Function.identity()
)
);
Import
import java.util.function.Function;
Map of Code to Title
If value হিসেবে পুরো Course দরকার না হয়:
Map<String, String> titleByCode =
courses.stream()
.collect(
Collectors.toMap(
Course::code,
Course::title
)
);
Result conceptually:
JAVA → Java Foundation
BACKEND → Backend Development
SYSTEM-DESIGN → System Design
Duplicate Keys
toMap() ব্যবহার করার সময় duplicate key খুব important।
Suppose:
JAVA → first Course
JAVA → second Course
এবং:
Collectors.toMap(
Course::code,
Function.identity()
)
use করি।
Duplicate key থাকলে collection operation fail করতে পারে।
কারণ Java জানে না:
কোন value রাখবে?
Duplicate Key Is Often a Domain Error
যদি Course code unique হওয়া উচিত, duplicate data silently overwrite করা ঠিক নাও হতে পারে।
এই ক্ষেত্রে failure useful।
Example business invariant:
Each Course code must be unique.
তাহলে duplicate key detect হওয়া উচিত।
Merge Function
কখনো duplicate key expected।
Suppose একই word-এর latest value রাখতে চাই।
toMap() merge function নিতে পারে।
Example:
Map<String, Course> byCode =
courses.stream()
.collect(
Collectors.toMap(
Course::code,
Function.identity(),
(
first,
second
) -> second
)
);
Meaning:
duplicate key হলে
second value রাখো
Keep First
(
first,
second
) -> first
Never Add Merge Logic Without Business Meaning
Duplicate key exception এড়ানোর জন্য blindly:
(first, second) -> second
লিখবেন না।
প্রথমে প্রশ্ন করুন:
Duplicate কেন সম্ভব?
Which value should win?
Duplicate itself কি error?
Data correctness first।
groupingBy()
এখন Collectors-এর সবচেয়ে useful operations-এর একটিতে আসি।
Suppose Course-এর status আছে:
enum CourseStatus {
DRAFT,
PUBLISHED,
ARCHIVED
}
Model:
record Course(
String code,
String title,
CourseStatus status
) {
}
আমরা চাই:
DRAFT
→ draft courses
PUBLISHED
→ published courses
ARCHIVED
→ archived courses
Use:
Map<CourseStatus, List<Course>> byStatus =
courses.stream()
.collect(
Collectors.groupingBy(
Course::status
)
);
groupingBy() Mental Model
Each element-এর জন্য একটি grouping key বের করা হয়।
Course
↓
status
↓
same status-এর Course একই group
Final result:
Map<Key, List<Element>>
Example Result
Conceptually:
DRAFT
→ [Course A, Course B]
PUBLISHED
→ [Course C, Course D]
ARCHIVED
→ [Course E]
Group by Price Category
Grouping key সবসময় existing field হতে হবে না।
Example:
Map<String, List<Course>> byPriceType =
courses.stream()
.collect(
Collectors.groupingBy(
course ->
course.priceInPaisa()
== 0
? "FREE"
: "PAID"
)
);
But if this category is meaningful domain logic, an enum or named method may be better than raw strings।
Better Domain Category
enum PriceType {
FREE,
PAID
}
Then:
static PriceType priceType(
Course course
) {
return course.priceInPaisa()
== 0
? PriceType.FREE
: PriceType.PAID;
}
Group:
Map<PriceType, List<Course>> byPriceType =
courses.stream()
.collect(
Collectors.groupingBy(
Main::priceType
)
);
Grouping Is More Than Lists
Default:
groupingBy(
classifier
)
gives:
Map<K, List<T>>
কিন্তু আমরা প্রতিটি group-এর জন্য অন্য collector-ও ব্যবহার করতে পারি।
এটাকে বলা হয়:
downstream collector
Count Per Group
Suppose আমরা Course list না, count চাই।
Desired:
DRAFT → 3
PUBLISHED → 10
ARCHIVED → 2
Use:
Map<CourseStatus, Long> counts =
courses.stream()
.collect(
Collectors.groupingBy(
Course::status,
Collectors.counting()
)
);
counting()
Collectors.counting() elements count করে এবং:
Long
result দেয়।
Grouping-এর সাথে খুব useful।
Grouping Titles Instead of Courses
Suppose:
status → List<String title>
Need:
Collectors.mapping(...)
mapping()
Example:
Map<CourseStatus, List<String>> titlesByStatus =
courses.stream()
.collect(
Collectors.groupingBy(
Course::status,
Collectors.mapping(
Course::title,
Collectors.toList()
)
)
);
What Is Happening?
Outer collector:
groupingBy(
Course::status,
...
)
groups by status।
Inside each group:
mapping(
Course::title,
toList()
)
means:
Course
→ title
→ List<String>
Downstream Collector Mental Model
groupingBy
→ group তৈরি করো
downstream collector
→ প্রতিটি group-এর ভিতরে result কী হবে?
Default:
List<Course>
Could be:
Long count
List<String> titles
Set<String> codes
Grouping Codes into Set
Map<CourseStatus, Set<String>> codesByStatus =
courses.stream()
.collect(
Collectors.groupingBy(
Course::status,
Collectors.mapping(
Course::code,
Collectors.toSet()
)
)
);
partitioningBy()
Sometimes grouping key শুধু:
true
false
এই দুইটি।
Example:
Course published কি না
Use:
Map<Boolean, List<Course>> partition =
courses.stream()
.collect(
Collectors.partitioningBy(
Course::published
)
);
Result
true
→ published courses
false
→ unpublished courses
partitioningBy() vs groupingBy()
partitioningBy() specifically boolean condition-এর জন্য।
true / false
groupingBy() arbitrary category key-এর জন্য।
Examples:
status
country
category
price range
Example — Paid vs Free
Map<Boolean, List<Course>> paid =
courses.stream()
.collect(
Collectors.partitioningBy(
course ->
course.priceInPaisa()
> 0
)
);
Then:
paid.get(
true
);
contains paid Courses।
But raw boolean key may sometimes reduce readability।
If:
FREE
PAID
is a real domain category, enum grouping can be clearer।
partitioningBy() with Counting
Map<Boolean, Long> counts =
courses.stream()
.collect(
Collectors.partitioningBy(
Course::published,
Collectors.counting()
)
);
Result conceptually:
true → 8
false → 3
Reduction
এখন reduce() নিয়ে কথা বলি।
Reduction মানে অনেক values combine করে একটি smaller result বা single value তৈরি করা।
Example:
1
2
3
4
reduce with addition
↓
10
Simple Sum
int total =
numbers.stream()
.reduce(
0,
(
sum,
value
) -> sum + value
);
reduce() Parameters
এখানে:
0
হলো:
identity
আর:
(
sum,
value
) -> sum + value
হলো:
accumulator
Identity
Identity হলো initial value যেটি operation-এর neutral starting value।
Addition-এর জন্য:
0
কারণ:
0 + x = x
Multiplication-এর জন্য identity:
1
কারণ:
1 × x = x
Sum Example
Input:
10
20
30
Start:
sum = 0
Then:
0 + 10
→ 10
10 + 20
→ 30
30 + 30
→ 60
Final:
60
Method Reference
Instead of:
(
sum,
value
) -> sum + value
we can write:
Integer::sum
So:
int total =
numbers.stream()
.reduce(
0,
Integer::sum
);
Product
int product =
numbers.stream()
.reduce(
1,
(
result,
value
) ->
result * value
);
Maximum Without Identity
Suppose:
List<Integer> numbers
Maximum খুঁজতে arbitrary identity choose করা dangerous হতে পারে।
Example:
0
identity দিলে negative-only data-তে wrong result হতে পারে।
Instead:
Optional<Integer> maximum =
numbers.stream()
.reduce(
Integer::max
);
Why Optional?
Empty Stream হলে maximum নেই।
So:
reduce(
Integer::max
)
returns:
Optional<Integer>
Minimum
Optional<Integer> minimum =
numbers.stream()
.reduce(
Integer::min
);
তবে Stream-এর dedicated:
min(...)
max(...)
operations অনেক সময় intent clearer করে।
আমরা general reduction concept বোঝার জন্য reduce() দেখছি।
Sum of Course Prices
Suppose Course prices:
long totalPrice =
courses.stream()
.map(
Course::priceInPaisa
)
.reduce(
0L,
Long::sum
);
Type flow:
Course
→ Long
→ one Long total
Primitive Streams Can Be Better for Numeric Aggregation
Java provides:
mapToInt()
mapToLong()
mapToDouble()
For prices:
long totalPrice =
courses.stream()
.mapToLong(
Course::priceInPaisa
)
.sum();
এটি numeric aggregation-এর intent আরও clearly express করে।
So if simple sum দরকার:
sum()
use করা preferable হতে পারে।
reduce() Is for Combining Values
Good mental model:
Many values
↓
combine
↓
one result
Examples:
sum
product
combined value
maximum
minimum
Do Not Use reduce() to Mutate a Collection
Bad idea:
List<String> result =
stream.reduce(
new ArrayList<>(),
(
list,
value
) -> {
list.add(
value
);
return list;
}
);
এটি mutable collection accumulation-এর জন্য reduce() misuse।
Use:
collect(...)
or:
toList()
Why collect() for Mutable Containers?
collect() specifically mutable result containers-এর accumulation support করার জন্য designed।
Examples:
List
Set
Map
StringBuilder-like accumulation
grouped structures
Reduction conceptually better fits immutable-style value combination।
collect() vs reduce()
A useful practical distinction:
collect()
→ many elements into a mutable/structured container
reduce()
→ many values into one combined value
Examples:
List<String>
→ collect
Map<String, Course>
→ collect
Grouped Map
→ collect
Sum
→ reduce or numeric sum()
Product
→ reduce
Single combined object/value
→ reduce may fit
Do Not Force reduce()
Suppose need count।
Could technically build a reduction।
But:
stream.count()
is clearer।
Need sum:
mapToLong(...).sum()
can be clearer।
Need max:
stream.max(...)
can be clearer।
Use specialized APIs when they communicate intent directly।
Counting Published Courses
Simple version:
long count =
courses.stream()
.filter(
Course::published
)
.count();
No need:
reduce(...)
Grouping Example — Courses by Status
Complete example:
Map<CourseStatus, List<Course>> grouped =
courses.stream()
.collect(
Collectors.groupingBy(
Course::status
)
);
Then:
List<Course> published =
grouped.get(
CourseStatus.PUBLISHED
);
Missing Group
যদি কোনো status-এর Course না থাকে:
grouped.get(
CourseStatus.ARCHIVED
)
may return:
null
because the Map may not contain that key।
Safer:
List<Course> archived =
grouped.getOrDefault(
CourseStatus.ARCHIVED,
List.of()
);
Grouping Does Not Automatically Create Every Enum Key
Suppose enum:
DRAFT
PUBLISHED
ARCHIVED
কিন্তু data-তে archived Course নেই।
groupingBy() necessarily:
ARCHIVED → []
entry তৈরি করবে না।
Only encountered groups expect করুন।
Frequency Counting with Grouping
Suppose tags:
Java
Backend
Java
OOP
Java
Backend
We can count:
Map<String, Long> frequencies =
tags.stream()
.collect(
Collectors.groupingBy(
Function.identity(),
Collectors.counting()
)
);
Result:
Java → 3
Backend → 2
OOP → 1
Compare with HashMap merge()
আগের lesson-এ frequency counter লিখেছিলাম:
Map<String, Integer> counts =
new HashMap<>();
for (
String tag
: tags
) {
counts.merge(
tag,
1,
Integer::sum
);
}
Stream version:
Map<String, Long> counts =
tags.stream()
.collect(
Collectors.groupingBy(
Function.identity(),
Collectors.counting()
)
);
দুইটিই valid।
Which Is Better?
যদি simple counting algorithm এবং mutable state explicit দেখতে চান:
HashMap + loop
খুব clear।
যদি existing Stream transformation-এর অংশ হিসেবে grouping/counting হয়:
groupingBy + counting
natural হতে পারে।
Complex Collector Pipelines
Collector APIs powerful হওয়ায় nested expressions দ্রুত difficult হয়ে যেতে পারে।
Example:
Collectors.groupingBy(
Course::status,
Collectors.mapping(
Course::title,
Collectors.toSet()
)
)
এটি এখনো readable।
কিন্তু যদি nesting অনেক বেড়ে যায়:
grouping
mapping
filtering
collectingAndThen
another nested collector
তাহলে code split বা named method consider করুন।
Clear Code Beats Clever Collector Code
Goal:
Reader যেন বুঝতে পারে result কী।
Not:
এক expression-এ সব Collectors ব্যবহার করা।
Practical Example — Titles by Status
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Course> courses =
List.of(
new Course(
"JAVA",
"Java Foundation",
CourseStatus.PUBLISHED
),
new Course(
"BACKEND",
"Backend Development",
CourseStatus.PUBLISHED
),
new Course(
"SYSTEM",
"System Design",
CourseStatus.DRAFT
)
);
Map<CourseStatus, List<String>> titlesByStatus =
courses.stream()
.collect(
Collectors.groupingBy(
Course::status,
Collectors.mapping(
Course::title,
Collectors.toList()
)
)
);
System.out.println(
titlesByStatus
);
}
enum CourseStatus {
DRAFT,
PUBLISHED,
ARCHIVED
}
record Course(
String code,
String title,
CourseStatus status
) {
}
}
Practical Example — Course Index
Map<String, Course> coursesByCode =
courses.stream()
.collect(
Collectors.toMap(
Course::code,
Function.identity()
)
);
Use:
Course course =
coursesByCode.get(
"JAVA"
);
Practical Example — Unique Topics
Set<String> topics =
courses.stream()
.flatMap(
course ->
course.topics()
.stream()
)
.collect(
Collectors.toSet()
);
Practical Example — Display String
String titles =
courses.stream()
.map(
Course::title
)
.collect(
Collectors.joining(
" | "
)
);
Result:
Java Foundation | Backend Development | System Design
Practical Example — Total Price
long total =
courses.stream()
.mapToLong(
Course::priceInPaisa
)
.sum();
যদিও lesson reduction নিয়ে, simple numeric sum-এর জন্য এই versionই clearer।
Practical Example — Manual Reduction
long total =
courses.stream()
.map(
Course::priceInPaisa
)
.reduce(
0L,
Long::sum
);
Same conceptual result।
Common Mistake 1 — Using toMap() Without Considering Duplicate Keys
If keys may duplicate:
Collectors.toMap(...)
এর behavior আগে decide করুন।
Duplicate:
error?
first wins?
last wins?
combine values?
Business rule explicit করুন।
Common Mistake 2 — Assuming Grouping Has Every Possible Key
No data:
ARCHIVED
means Map-এ:
ARCHIVED
key necessarily থাকবে না।
Common Mistake 3 — Raw Boolean Maps Everywhere
Map<Boolean, List<Course>>
technically fine।
কিন্তু domain concept যদি:
FREE
PAID
হয়, enum map clearer হতে পারে।
Common Mistake 4 — Using reduce() for Lists
Do not manually mutate an ArrayList inside reduce()।
Use:
toList()
collect()
Common Mistake 5 — Using collect() for Simple Sum
Possible হলেও unnecessary abstraction হতে পারে।
Prefer:
mapToLong(...)
.sum()
when appropriate।
Common Mistake 6 — Incorrect Identity
Suppose multiplication:
.reduce(
0,
(
result,
value
) -> result * value
)
Everything becomes:
0
because multiplication identity should be:
1
Common Mistake 7 — Bad Maximum Identity
For negative values:
.reduce(
0,
Integer::max
)
can produce wrong result।
Example input:
-10
-5
Result would incorrectly include:
0
Use no-identity reduction or dedicated:
max(...)
Common Mistake 8 — Using Collectors Without Understanding Result Type
Always reason about:
Stream<T>
↓
Collector
↓
Result type
Example:
groupingBy(
Course::status
)
produces conceptually:
Map<CourseStatus, List<Course>>
Common Mistake 9 — Huge Nested Collector Expressions
If collector expression takes significant effort to decode, break it into:
named functions
named classifiers
named downstream collectors
or a normal loop।
Common Mistake 10 — Assuming Functional Means No Complexity
This:
groupingBy(...)
still needs:
memory
hashing
element processing
Collector syntax does not remove algorithmic cost।
Practice 1 — Collect to Set
Convert:
Stream<String>
to unique Set<String>।
Solution
Set<String> result =
stream.collect(
Collectors.toSet()
);
Practice 2 — Join Names
Given:
Stream<String>
produce:
Sakib, Subu, Sumu
Solution
String result =
stream.collect(
Collectors.joining(
", "
)
);
Practice 3 — Build Map
Given Course, build:
code → title
Solution
Map<String, String> result =
courses.stream()
.collect(
Collectors.toMap(
Course::code,
Course::title
)
);
Practice 4 — Duplicate Keys
Two Courses have the same code।
Should you automatically keep the last one?
Answer
Not unless business rules explicitly say so।
Duplicate Course code may indicate invalid data and should often fail rather than silently overwrite।
Practice 5 — Group by Status
Solution
Map<CourseStatus, List<Course>> result =
courses.stream()
.collect(
Collectors.groupingBy(
Course::status
)
);
Practice 6 — Count by Status
Solution
Map<CourseStatus, Long> result =
courses.stream()
.collect(
Collectors.groupingBy(
Course::status,
Collectors.counting()
)
);
Practice 7 — Titles by Status
Solution
Map<CourseStatus, List<String>> result =
courses.stream()
.collect(
Collectors.groupingBy(
Course::status,
Collectors.mapping(
Course::title,
Collectors.toList()
)
)
);
Practice 8 — Partition
Split numbers into:
even
not even
Solution
Map<Boolean, List<Integer>> result =
numbers.stream()
.collect(
Collectors.partitioningBy(
number ->
number % 2
== 0
)
);
Practice 9 — Sum with Reduce
Solution
int total =
numbers.stream()
.reduce(
0,
Integer::sum
);
Practice 10 — Product
Solution
int product =
numbers.stream()
.reduce(
1,
(
result,
value
) ->
result * value
);
Practice 11 — Maximum
Why may this be wrong?
numbers.stream()
.reduce(
0,
Integer::max
);
Answer
If all values are negative, 0 becomes an artificial candidate।
Better:
numbers.stream()
.reduce(
Integer::max
);
which returns an Optional<Integer>।
Practice 12 — Collect or Reduce?
Need:
Course code → Course
Answer
collect()
with:
Collectors.toMap(...)
Practice 13 — Collect or Reduce?
Need product of all integers।
Answer
reduce()
is a natural choice।
Practice 14 — Best API
Need total price of all courses।
Which is clearer?
reduce()
mapToLong().sum()
Answer
Usually:
courses.stream()
.mapToLong(
Course::priceInPaisa
)
.sum();
because intent is explicitly numeric summation।
True or False
collect()is a terminal operation.Collectors.toSet()can remove duplicate values.joining()produces a String.toMap()can encounter duplicate-key problems.- Duplicate keys should always silently overwrite old values.
groupingBy()commonly produces a Map of groups.- Default
groupingBy()groups values into Lists. counting()can be used as a downstream collector.mapping()can transform values inside each group.partitioningBy()groups by arbitrary String keys.reduce()can combine many values into one result.- Addition identity is
0. - Multiplication identity is
1. reduce()is the preferred way to mutate and build an ArrayList.- Specialized APIs such as
sum()may be clearer than genericreduce().
Answers
1. True
2. True
3. True
4. True
5. False
6. True
7. True
8. True
9. True
10. False
11. True
12. True
13. True
14. False
15. True
Knowledge Check
Question 1
collect() কী করে?
Question 2
Collectors.toSet() কখন useful?
Question 3
joining() কী result তৈরি করে?
Question 4
toMap()-এ duplicate key কেন important?
Question 5
groupingBy() কী করে?
Question 6
Downstream collector কী?
Question 7
counting() এবং mapping() grouping-এর সাথে কীভাবে useful?
Question 8
partitioningBy() এবং groupingBy()-এর difference কী?
Question 9
Reduction কী?
Question 10
reduce()-এ identity value কী?
Question 11
কেন incorrect identity wrong result দিতে পারে?
Question 12
collect() এবং reduce() কখন ব্যবহার করবেন?
Knowledge Check Answers
Answer 1
collect() Stream-এর elements নিয়ে একটি structured result তৈরি করে।
Examples:
List
Set
Map
Grouped Map
String
Answer 2
যখন result একটি unique-value Set হওয়া উচিত।
Example:
unique course topics
Answer 3
একাধিক String element delimiter ব্যবহার করে combine করে একটি single String তৈরি করে।
Answer 4
Map-এ একটি key-এর জন্য একটি value থাকে। Duplicate key এলে decide করতে হয় duplicate invalid, প্রথম value রাখা হবে, দ্বিতীয় value রাখা হবে, নাকি values combine হবে।
Answer 5
প্রতিটি element-এর জন্য একটি classification key বের করে একই key-এর elements একই group-এ collect করে।
Default result conceptually:
Map<K, List<T>>
Answer 6
groupingBy()-এর প্রতিটি group-এর elements কীভাবে final result-এ collect হবে সেটি downstream collector define করে।
Example:
List তৈরি
count করা
titles map করা
Set তৈরি
Answer 7
counting() প্রতিটি group-এর element count করতে পারে।
mapping() group-এর original values অন্য type-এ transform করে তারপর downstream collector-এ পাঠায়।
Answer 8
partitioningBy() একটি boolean condition অনুযায়ী:
true
false
দুইটি partition তৈরি করে।
groupingBy() arbitrary classification key অনুযায়ী multiple groups তৈরি করতে পারে।
Answer 9
অনেক values combine করে একটি smaller বা single result তৈরি করার process হলো reduction।
Example:
sum
product
maximum
Answer 10
Identity হলো reduction-এর starting neutral value।
Example:
Addition:
0
Multiplication:
1
Answer 11
Identity operation-এর neutral value না হলে এটি actual input-এর বাইরে একটি artificial value হিসেবে result পরিবর্তন করতে পারে।
Answer 12
Structured mutable/container-style result-এর জন্য সাধারণত:
collect()
আর values combine করে single result-এর জন্য:
reduce()
useful।
তবে specialized operations:
sum()
count()
min()
max()
থাকলে সেগুলো আরও expressive হতে পারে।
Practical Collector Selection Guide
Need List:
toList()
অথবা:
Collectors.toList()
Need Set:
Collectors.toSet()
Need String:
Collectors.joining(...)
Need key-value index:
Collectors.toMap(...)
Need groups:
Collectors.groupingBy(...)
Need true/false split:
Collectors.partitioningBy(...)
Need count per group:
Collectors.groupingBy(
keyFunction,
Collectors.counting()
)
Need transformed values inside groups:
Collectors.mapping(...)
Need one combined value:
reduce(...)
Core Mental Model
Stream processing-এর শেষে নিজেকে প্রশ্ন করুন:
আমি final result হিসেবে কী চাই?
If answer:
List
Set
Map
Grouped Map
String
think:
collect
If answer:
একটি combined value
think:
reduction
Example:
Courses
→ group by status
→ Map<CourseStatus, List<Course>>
Topics
→ unique
→ Set<String>
Titles
→ join
→ String
Prices
→ add together
→ long total
Lesson Summary
এই lesson-এ আমরা Stream results aggregate এবং organize করার গুরুত্বপূর্ণ techniques শিখেছি।
আমরা শিখেছি:
collect()একটি terminal operationCollectorspredefined collection strategies দেয়Collectors.toList()List result তৈরি করতে পারেCollectors.toSet()Set result তৈরি করেjoining()multiple Strings combine করেtoMap()key-value structure তৈরি করেtoMap()-এর duplicate-key behavior carefully design করতে হয়Function.identity()input value unchanged return করেgroupingBy()values category অনুযায়ী group করে- Default grouping result সাধারণত
Map<K, List<T>> - Downstream collectors group result customize করে
counting()group size calculate করতে পারেmapping()grouped values transform করতে পারেpartitioningBy()boolean condition অনুযায়ী দুইটি partition তৈরি করেreduce()many values combine করে single result তৈরি করতে পারে- Reduction-এর identity neutral value হওয়া প্রয়োজন
- Wrong identity wrong result দিতে পারে
reduce()mutable List-building-এর জন্য appropriate নয়collect()structured result accumulation-এর জন্য better fit- Numeric aggregation-এর জন্য
sum()-এর মতো specialized APIs clearer হতে পারে - Powerful collector expressions readability নষ্ট করলে simpler code prefer করা উচিত
সবচেয়ে গুরুত্বপূর্ণ distinction:
collect()
→ structure বানাও
reduce()
→ values combine করো
আর practical rule:
Use the most specific API
that clearly communicates the result you want.
Next Lesson
পরবর্তী lesson:
Optional and Modern Null Handling
আমরা শিখব:
nullকেন problematic হতে পারেOptional<T>কীOptional.empty()Optional.of()Optional.ofNullable()isPresent()isEmpty()ifPresent()map()flatMap()filter()orElse()orElseGet()orElseThrow()or()- Optional chaining
- Optional return type কখন appropriate
- Optional field বা parameter হিসেবে blindly ব্যবহার করা কেন ভালো নয়
Optional.get()কেন avoid করা উচিত- Modern null-handling strategies