Object-Oriented Programming Foundations
Designing Immutable Objects
You are viewing a free preview lesson.
Lesson Overview
Mutable object-এর state object creation-এর পরে পরিবর্তন করা যায়।
course.publish();
enrollment.completeLessons(3);
এ ধরনের পরিবর্তন অনেক domain object-এর জন্য natural।
কিন্তু সব objects-এর state পরিবর্তনশীল হওয়া প্রয়োজন নেই।
Examples:
Course code
Email address
Money amount
Geographic coordinate
Date range
Enrollment ID
একটি course code যদি হয়:
JAVA-FOUNDATION
তাহলে একই object পরে অন্য code represent করা confusing:
BACKEND-DEVELOPMENT
এ ধরনের value একবার তৈরি হওয়ার পরে অপরিবর্তিত থাকলে code বুঝতে, validate করতে এবং safely share করতে সহজ হয়।
যে object creation-এর পরে observable state পরিবর্তন করতে দেয় না, তাকে immutable object বলা হয়।
এই lesson-এ আমরা শিখব:
- Mutable এবং immutable object-এর পার্থক্য
finalfields- Constructor-only initialization
- Setters বাদ দেওয়া
- Immutable value objects
- Shallow এবং deep immutability
- Defensive copying
- Immutable collections
- Immutability-এর practical benefits
- কোথায় immutability appropriate
- কোথায় controlled mutability প্রয়োজন
Learning Objectives
এই lesson শেষ করার পর আপনি পারবেন:
- Immutable object কী ব্যাখ্যা করতে
finalfield-এর behavior বুঝতে- Constructor দিয়ে complete immutable state তৈরি করতে
- Setter ছাড়া value object design করতে
- Mutable internal object safely handle করতে
- Defensive copy ব্যবহার করতে
- Shallow এবং deep immutability আলাদা করতে
- Immutable value object-এর equality design করতে
- Immutability এবং thread safety-এর relationship বুঝতে
- Entity এবং value object-এর জন্য appropriate mutability নির্বাচন করতে
Mutable Objects
Mutable object-এর state creation-এর পরে পরিবর্তন করা যায়।
public class Course {
private String title;
private boolean published;
public boolean changeTitle(
String newTitle
) {
if (
newTitle == null
|| newTitle.isBlank()
) {
return false;
}
title =
newTitle.strip();
return true;
}
public boolean publish() {
if (published) {
return false;
}
published = true;
return true;
}
}
এই object mutable কারণ:
course.changeTitle(...);
course.publish();
তার state পরিবর্তন করে।
Mutable হওয়া automatically bad design নয়।
Course publication status বা enrollment progress naturally পরিবর্তন হতে পারে।
Immutable Objects
Immutable object creation-এর পরে নিজের observable state পরিবর্তন করতে দেয় না।
Example:
public final class CourseCode {
private final String value;
public CourseCode(
String value
) {
if (
value == null
|| value.isBlank()
) {
throw new IllegalArgumentException(
"Course code is required."
);
}
this.value =
value.strip()
.toUpperCase();
}
public String getValue() {
return value;
}
}
Object creation:
CourseCode code =
new CourseCode(
"java-foundation"
);
Stored value:
JAVA-FOUNDATION
এরপর code change করার কোনো method নেই।
code.setValue(...);
code.changeValue(...);
এ ধরনের API expose করা হয়নি।
What Makes an Object Immutable?
একটি class immutable design করতে সাধারণত প্রয়োজন:
- Fields
private - Fields যতটা সম্ভব
final - Constructor complete state initialize করবে
- কোনো setter থাকবে না
- State-changing method থাকবে না
- Mutable internal object direct expose করা হবে না
- Mutable constructor argument defensiveভাবে copy করা হবে
- Class inheritance-এর মাধ্যমে mutability introduce করা prevent করতে class
finalকরা যেতে পারে
final Fields
A final field একবার assign হওয়ার পরে reassign করা যায় না।
private final String value;
Constructor-এ assignment:
this.value =
value;
পরে invalid:
this.value =
"NEW-VALUE";
Compiler error হবে।
final Must Be Initialized
Blank final field:
private final String code;
Constructor-এর প্রতিটি valid execution path-এ initialize করতে হবে।
public CourseCode(
String code
) {
this.code =
code;
}
যদি কোনো execution path field assign না করে, code compile নাও করতে পারে।
final Reference Does Not Make the Object Immutable
Consider:
private final List<String> lessons;
final means:
lessons reference cannot point to another List
কিন্তু referenced list mutable হলে:
lessons.add(
"New Lesson"
);
still possible।
Important distinction:
finalreference reassign prevent করে; referenced object-এর internal mutation prevent করে না।
Shallow Immutability
public final class CoursePlan {
private final List<String> lessons;
public CoursePlan(
List<String> lessons
) {
this.lessons =
lessons;
}
public List<String> getLessons() {
return lessons;
}
}
Fields final হলেও class সত্যিকার অর্থে immutable নয়।
Caller:
List<String> lessonTitles =
new ArrayList<>();
lessonTitles.add(
"Introduction"
);
CoursePlan plan =
new CoursePlan(
lessonTitles
);
lessonTitles.add(
"Classes and Objects"
);
plan-এর internal stateও পরিবর্তিত হয়েছে, কারণ same list reference share করছে।
Mutation Through a Getter
Caller আরও করতে পারে:
plan.getLessons()
.clear();
Getter internal mutable list direct return করেছে।
Encapsulation bypass হয়েছে।
Defensive Copying
Constructor argument copy করে internal state protect করা যায়।
public CoursePlan(
List<String> lessons
) {
if (lessons == null) {
throw new IllegalArgumentException(
"Lessons are required."
);
}
this.lessons =
List.copyOf(
lessons
);
}
List.copyOf() একটি unmodifiable copy তৈরি করে।
এখন caller original list modify করলেও CoursePlan state change হবে না।
Returning an Immutable View or Copy
Getter:
public List<String> getLessons() {
return lessons;
}
যদি constructor List.copyOf() ব্যবহার করে immutable list store করে, returned list caller modify করতে পারবে না।
plan.getLessons()
.add("Another Lesson");
Runtime-এ:
UnsupportedOperationException
আরও explicitভাবে:
public List<String> getLessons() {
return List.copyOf(
lessons
);
}
Internal collection already immutable হলে repeated copy সবসময় প্রয়োজন নাও হতে পারে।
Immutable Elements Matter Too
Suppose:
private final List<Lesson> lessons;
List unmodifiable হলেও Lesson objects mutable হতে পারে।
plan.getLessons()
.get(0)
.changeTitle(
"Changed"
);
Collection structure change হয়নি, কিন্তু nested object change হয়েছে।
Deep immutability-এর জন্য contained elements-ও immutable হতে হবে অথবা defensive copies প্রয়োজন।
Shallow vs Deep Immutability
Shallow Immutability
Object-এর own fields reassign করা যায় না, কিন্তু referenced objects mutate হতে পারে।
Deep Immutability
Object graph-এর externally observable কোনো অংশই mutation allow করে না।
Deep immutability অর্জন করতে:
- Nested objects immutable হতে পারে
- Collections immutable হতে পারে
- Mutable inputs copy করতে হয়
- Mutable outputs direct expose করা যাবে না
String Helps Immutability
String immutable।
private final String value;
Getter:
public String getValue() {
return value;
}
safe, কারণ caller String object-এর content modify করতে পারে না।
String operation নতুন value তৈরি করে।
String upper =
value.toUpperCase();
Original value unchanged থাকে।
Immutable Value Objects
Value object সাধারণত identity-এর পরিবর্তে value দ্বারা defined।
Examples:
CourseCode
EmailAddress
Money
DateRange
Coordinate
দুইটি CourseCode object same normalized value ধারণ করলে logically equal হওয়া উচিত।
CourseCode first =
new CourseCode(
"java-foundation"
);
CourseCode second =
new CourseCode(
"JAVA-FOUNDATION"
);
Expected:
first.equals(second)
true
Complete Immutable CourseCode
import java.util.Objects;
public final class CourseCode {
private final String value;
public CourseCode(
String value
) {
if (
value == null
|| value.isBlank()
) {
throw new IllegalArgumentException(
"Course code is required."
);
}
String normalizedValue =
value.strip()
.toUpperCase();
if (
!normalizedValue.matches(
"[A-Z0-9-]+"
)
) {
throw new IllegalArgumentException(
"Course code may contain only letters, numbers, and hyphens."
);
}
this.value =
normalizedValue;
}
public String getValue() {
return value;
}
@Override
public boolean equals(
Object other
) {
if (this == other) {
return true;
}
if (
other == null
|| getClass()
!= other.getClass()
) {
return false;
}
CourseCode courseCode =
(CourseCode) other;
return value.equals(
courseCode.value
);
}
@Override
public int hashCode() {
return Objects.hash(
value
);
}
@Override
public String toString() {
return value;
}
}
Why the Class Is final
public final class CourseCode
Class final হলে অন্য class এটিকে extend করতে পারবে না।
Without final, subclass additional mutable state বা behavior introduce করতে পারে।
class MutableCourseCode
extends CourseCode {
}
Immutable base class-এর contract দুর্বল হতে পারে।
final immutable class-এর design intent protect করে।
Constructor Establishes the Complete Value
public CourseCode(
String value
)
Constructor:
- Required input নেয়
- Input validate করে
- Input normalize করে
- Final field initialize করে
Object creation-এর পরে কোনো intermediate state নেই।
null
blank
partially normalized
কোনোটিই successfully created object-এর state হতে পারে না।
No Setter
There is intentionally no:
setValue()
changeValue()
Course code change দরকার হলে নতুন object তৈরি করতে হবে।
CourseCode newCode =
new CourseCode(
"BACKEND-DEVELOPMENT"
);
Immutable object update মানে সাধারণত replacement value তৈরি করা।
Using Immutable Objects in Mutable Entities
একটি Course entity mutable হতে পারে, কিন্তু তার code immutable value object হতে পারে।
public class Course {
private final CourseCode code;
private String title;
private boolean published;
public Course(
CourseCode code,
String title
) {
if (code == null) {
throw new IllegalArgumentException(
"Course code is required."
);
}
if (
title == null
|| title.isBlank()
) {
throw new IllegalArgumentException(
"Course title is required."
);
}
this.code = code;
this.title = title.strip();
this.published = false;
}
public boolean changeTitle(
String newTitle
) {
if (
newTitle == null
|| newTitle.isBlank()
) {
return false;
}
title =
newTitle.strip();
return true;
}
public boolean publish() {
if (published) {
return false;
}
published = true;
return true;
}
public CourseCode getCode() {
return code;
}
public String getTitle() {
return title;
}
public boolean isPublished() {
return published;
}
}
এই design-এ:
Course entity mutable
CourseCode value immutable
এটি common এবং practical combination।
Immutable Does Not Mean Every Field Must Be Primitive
Immutable object অন্য immutable objects reference করতে পারে।
public final class EnrollmentIdentity {
private final long learnerId;
private final CourseCode courseCode;
public EnrollmentIdentity(
long learnerId,
CourseCode courseCode
) {
if (learnerId <= 0) {
throw new IllegalArgumentException(
"Learner ID must be positive."
);
}
if (courseCode == null) {
throw new IllegalArgumentException(
"Course code is required."
);
}
this.learnerId = learnerId;
this.courseCode = courseCode;
}
}
CourseCode immutable হওয়ায় reference safely share করা যায়।
Benefits of Immutability
Easier Reasoning
একবার object inspect করলে জানেন state unexpectedly change হবে না।
CourseCode code =
course.getCode();
অন্য method code object mutate করবে না।
Safer Sharing
একই immutable object multiple objects share করতে পারে।
Enrollment first =
new Enrollment(
nur,
course
);
Enrollment second =
new Enrollment(
jalisa,
course
);
Immutable value object shared হলে synchronization concern কমে।
Thread Safety
Immutable object-এর state change হয় না।
Multiple threads একই object read করলে mutation race থাকে না।
তাই properly immutable objects inherently thread-safe for read-only use।
তবে immutable object external mutable dependency use করলে পুরো operation thread-safe হবে—এমন নয়।
Stable Equality and Hash Code
Equality fields immutable হলে object-এর hashCode() lifetime-এর মধ্যে change হয় না।
Hash-based collections-এর জন্য এটি safer।
Easier Testing
Input object method call-এর মধ্যে silently change হবে না।
Expected result reason করা সহজ।
Safe Caching
Immutable values cache এবং reuse করা comparatively safe।
Same object future caller mutate করতে পারবে না।
Costs and Trade-Offs
Immutability free নয়।
New Objects Must Be Created
State variation represent করতে নতুন object দরকার।
Money increased =
original.add(
additional
);
Original unchanged।
High-frequency update scenario-তে allocation বাড়তে পারে, যদিও JVM অনেক allocation optimize করতে পারে।
Large Object Graph Copying
Large mutable structures deeply copy করা expensive হতে পারে।
Persistent data structures বা controlled mutation better হতে পারে।
Some Domains Are Naturally Stateful
Enrollment progress naturally changes।
0% → 20% → 80% → 100%
প্রতিটি lesson completion-এ entirely new enrollment object তৈরি করা possible, কিন্তু beginner domain model-এ controlled mutable entity simpler হতে পারে।
Immutability Is Not an Absolute Rule
Avoid simplistic rule:
Every object must be immutable.
Better:
Prefer immutability for values and stable configuration; use controlled mutability where state transitions are central to the domain.
Likely immutable:
CourseCode
EmailAddress
Money
DateRange
Coordinates
Likely controlled mutable:
Course publication status
Enrollment progress
Shopping cart
Order workflow
Account state
Entity vs Value Object
Entity
Identity গুরুত্বপূর্ণ।
State সময়ের সঙ্গে change হতে পারে।
Example:
Course
Learner
Enrollment
A course title change হলেও একই course থাকতে পারে।
Value Object
Value গুরুত্বপূর্ণ।
Same values মানে logically same value।
Example:
CourseCode
Money
EmailAddress
Value change মানে নতুন value object।
Immutable Money Example
Money floating-point দিয়ে model করা risky।
Immutable money value object minor units ব্যবহার করতে পারে।
import java.util.Objects;
public final class Money {
private final long amountInPaisa;
public Money(
long amountInPaisa
) {
if (amountInPaisa < 0) {
throw new IllegalArgumentException(
"Amount cannot be negative."
);
}
this.amountInPaisa =
amountInPaisa;
}
public Money add(
Money other
) {
if (other == null) {
throw new IllegalArgumentException(
"Other amount is required."
);
}
return new Money(
amountInPaisa
+ other.amountInPaisa
);
}
public Money applyDiscount(
int percentage
) {
if (
percentage < 0
|| percentage > 100
) {
throw new IllegalArgumentException(
"Discount must be between 0 and 100."
);
}
long discount =
amountInPaisa
* percentage
/ 100;
return new Money(
amountInPaisa
- discount
);
}
public long getAmountInPaisa() {
return amountInPaisa;
}
@Override
public boolean equals(
Object other
) {
if (this == other) {
return true;
}
if (
other == null
|| getClass()
!= other.getClass()
) {
return false;
}
Money money =
(Money) other;
return amountInPaisa
== money.amountInPaisa;
}
@Override
public int hashCode() {
return Objects.hash(
amountInPaisa
);
}
@Override
public String toString() {
return "BDT "
+ amountInPaisa
/ 100.0;
}
}
Immutable Operations Return New Objects
Money originalPrice =
new Money(
499_000L
);
Money discountedPrice =
originalPrice
.applyDiscount(20);
Now:
originalPrice = BDT 4990.0
discountedPrice = BDT 3992.0
originalPrice change হয়নি।
applyDiscount() নতুন Money object return করেছে।
Avoid Mutation-Looking Method Names
Immutable object-এ:
money.setAmount(...);
money.changeAmount(...);
avoid করা ভালো।
Operations result value return করতে পারে:
money.add(other);
money.applyDiscount(20);
Method name থেকে caller বুঝতে হবে original object unchanged থাকবে কি না।
Java standard library-তে String একই approach ব্যবহার করে।
String original =
" java ";
String normalized =
original.strip()
.toUpperCase();
original unchanged থাকে।
Records as Immutable Data Carriers
Modern Java-তে record concise immutable-style data carrier তৈরি করতে পারে।
Example:
public record CourseCode(
String value
) {
public CourseCode {
if (
value == null
|| value.isBlank()
) {
throw new IllegalArgumentException(
"Course code is required."
);
}
value =
value.strip()
.toUpperCase();
}
}
Record automatically provide করে:
- Final components
- Accessor
equals()hashCode()toString()
Usage:
CourseCode code =
new CourseCode(
"java-foundation"
);
System.out.println(
code.value()
);
তবে records:
- সব domain entities-এর replacement নয়
- Components-এর referenced objects deeply immutable করে না
- Mutable collection component থাকলে mutation leak হতে পারে
এই course-এ normal classes প্রথমে শেখানো হয়েছে, যাতে underlying concepts clear থাকে।
Engineering Note: Unmodifiable Is Not Always Immutable
An unmodifiable collection caller-কে add বা remove করতে দেয় না।
কিন্তু যদি এটি mutable elements contain করে, elements change হতে পারে।
List<Lesson> lessons =
List.copyOf(
sourceLessons
);
List structure immutable-style।
But:
lessons.get(0)
.changeTitle(
"Updated"
);
possible, যদি Lesson mutable।
Therefore:
Unmodifiable collection
এবং:
Deeply immutable object graph
এক জিনিস নয়।
Common Mistakes
Assuming final Means Immutable
private final List<String> values;
List still mutable হতে পারে।
Keeping Setters in an Immutable Class
public void setValue(
String value
) {
this.value =
value;
}
এটি immutability ভেঙে দেয়।
Returning Mutable Internal State
public List<String> getLessons() {
return lessons;
}
Caller internal state modify করতে পারে।
Storing Mutable Constructor Arguments Directly
this.lessons =
lessons;
Caller original list mutate করলে object change হয়।
Using Mutable Equality Fields
Immutable value object-এর equality stable values-এর ওপর based হওয়া উচিত।
Making Every Entity Immutable Without Need
Naturally changing workflows unnecessarily complex হয়ে যেতে পারে।
Returning this After Mutation and Calling It Immutable
public Money add(
Money other
) {
amount +=
other.amount;
return this;
}
Object mutate হচ্ছে।
Method new object return করলেও implementation immutable নয়, যদি existing state change হয়।
Practice Exercises
Exercise 1: Create an Immutable EmailAddress
Requirements:
Value required
Surrounding spaces remove
Lowercase normalize
Must contain @
No setter
Value-based equals and hashCode
Exercise 2: Identify Mutation Leaks
Review:
public final class CoursePlan {
private final List<String> lessons;
public CoursePlan(
List<String> lessons
) {
this.lessons =
lessons;
}
public List<String> getLessons() {
return lessons;
}
}
Identify every way external code can modify the plan।
Exercise 3: Apply Defensive Copying
Refactor CoursePlan using:
List.copyOf(...)
Reject:
- Null list
- Null lesson titles
- Blank lesson titles
Exercise 4: Entity or Value Object
Classify:
Learner
CourseCode
Enrollment
Money
EmailAddress
Course
Explain which should likely be immutable।
Exercise 5: Immutable Operation
Implement:
Money subtract(
Money other
)
Rules:
otherrequired- Result cannot be negative
- Original objects unchanged
- Return a new
Money
Exercise 6: Find the False Immutability
public final class Schedule {
private final Date startDate;
public Date getStartDate() {
return startDate;
}
}
Explain why mutable Date can break immutability।
Suggest defensive copying or a modern immutable date type such as LocalDateTime।
Exercise 7: Choose Controlled Mutability
For course enrollment progress, compare:
- Mutating the existing
Enrollment - Returning a new
Enrollmentafter every completed lesson
Discuss simplicity, auditability, and allocation trade-offs।
Predict the Result
Question 1
final List<String> lessons =
new ArrayList<>();
lessons.add(
"Introduction"
);
Will it compile?
Question 2
final List<String> lessons =
new ArrayList<>();
lessons =
new ArrayList<>();
Will reassignment compile?
Question 3
Money original =
new Money(
100_000L
);
Money discounted =
original.applyDiscount(
20
);
What is the original amount after the call?
Question 4
If CourseCode has no setters and all fields are private final immutable values, can caller change its value after construction?
Predict the Result Answers
Answer 1
হ্যাঁ।
final reference same list point করছে; list-এর content mutate করা যায়।
Answer 2
না।
Final reference reassign করা যায় না।
Answer 3
100000 paisa
Method নতুন object return করে; original unchanged।
Answer 4
না, normal public API দিয়ে change করা যাবে না।
Knowledge Check
Question 1
Immutable object কী?
Question 2
final field কী prevent করে?
Question 3
final reference কি nested object mutation prevent করে?
Question 4
Defensive copy কী?
Question 5
Mutable constructor argument direct store করা risky কেন?
Question 6
Mutable internal collection getter দিয়ে return করা risky কেন?
Question 7
Shallow এবং deep immutability-এর difference কী?
Question 8
Value objects immutability-এর ভালো candidate কেন?
Question 9
Immutable object concurrency-তে useful কেন?
Question 10
সব domain entity immutable হওয়া উচিত কি?
Question 11
Immutable operation existing object change না করে কী করে?
Question 12
Record কি referenced mutable object-কে deeply immutable করে?
Knowledge Check Answers
Answer 1
যে object creation-এর পরে observable state পরিবর্তন করতে দেয় না।
Answer 2
Field value বা reference reassign করা।
Answer 3
না।
Answer 4
External mutable value-এর independent copy তৈরি করে internal state হিসেবে রাখা বা return করা।
Answer 5
Caller original object mutate করলে immutable class-এর internal stateও change হতে পারে।
Answer 6
Caller controlled methods bypass করে internal state change করতে পারে।
Answer 7
Shallow immutability own fields protect করে। Deep immutability nested object graph-এর mutationও prevent করে।
Answer 8
তাদের identity value দ্বারা determined এবং value change হলে নতুন value represent করা natural।
Answer 9
State change না হওয়ায় concurrent readers-এর মধ্যে mutation race থাকে না।
Answer 10
না। Naturally stateful entities controlled mutable হতে পারে।
Answer 11
নতুন object বা value return করে।
Answer 12
না। Record components mutable object reference করলে nested state mutate হতে পারে।
Lesson Summary
এই lesson-এ আমরা শিখেছি:
- Mutable object-এর state creation-এর পরে change হয়
- Immutable object observable state change করতে দেয় না
- Fields private এবং final রাখা immutability-এর foundation
- Constructor complete valid state initialize করে
- Immutable class setters expose করে না
finalreference nested object mutation prevent করে না- Mutable inputs direct store করলে mutation leak হয়
- Defensive copying internal state protect করে
- Mutable internal values direct return করা উচিত নয়
- Unmodifiable collection deeply immutable নাও হতে পারে
- Nested elementsও immutable হলে deep immutability সহজ হয়
Stringimmutable হওয়ায় safely share করা যায়- Value objects immutability-এর strong candidates
- Immutable equality এবং hash code stable থাকে
- Immutable objects reasoning, testing, sharing এবং concurrency সহজ করে
- Immutable operation original object mutate না করে নতুন object return করে
- Entities এবং value objects-এর mutability requirements আলাদা
- Controlled mutability naturally stateful workflows-এর জন্য appropriate
finalclass subclass-based mutation contract prevent করতে সাহায্য করে- Records concise immutable-style data carrier provide করে
- Records deep immutability automatically guarantee করে না
- Immutability একটি design tool, universal rule নয়
Next Lesson
পরবর্তী lesson:
Packages and Organizing Java Code
আমরা শিখব:
- Package কী
- Package declaration
- Import statement
- Class file organization
- Package naming conventions
- Package-private access
- Organizing code by feature
- Avoiding large utility and model packages
- Basic project structure for a maintainable Java application