Professional Java Practices
Immutability and Defensive Programming
You are viewing a free preview lesson.
Lesson Overview
Java-তে object তৈরি করা সহজ।
কিন্তু reliable object design-এর আসল challenge হলো:
Object-এর state কে পরিবর্তন করতে পারবে?
কখন পরিবর্তন করা যাবে?
কোন state invalid?
কোন reference বাইরে leak হচ্ছে?
Collection বাইরে expose করলে কী হতে পারে?
একটি class compile করলেই সেটি well-designed হয় না।
Strong object design চেষ্টা করে:
Invalid state prevent করতে
Unexpected mutation reduce করতে
Internal state protect করতে
Clear ownership রাখতে
এই lesson-এর দুইটি central concept:
Immutability
Defensive programming
Immutability মানে object তৈরি হওয়ার পর তার observable state আর পরিবর্তন হয় না।
Defensive programming মানে class এমনভাবে design করা যাতে caller ভুল করলেও বা unexpected input দিলেও object সহজে invalid state-এ না যায়।
এই lesson-এ আমরা শিখব:
- Mutable vs immutable objects
finalfields- Constructor validation
- Immutable value objects
- Defensive copying
- Mutation leaks
- Collections safely expose করা
- Returning snapshots
- Mutable inputs থেকে protection
- Immutable collections
List.copyOf()- Array defensive copies
- Shallow vs deep immutability
- Protecting invariants
- When mutability is appropriate
Learning Objectives
এই lesson শেষে আপনি পারবেন:
- Mutable এবং immutable object distinguish করতে
finalfield-এর actual guarantee explain করতে- Immutable value object design করতে
- Constructor-এ invalid state prevent করতে
- Defensive copies তৈরি করতে
- Internal collection mutation leak identify করতে
List.copyOf()ব্যবহার করতে- Mutable input reference safely handle করতে
- Shallow এবং deep immutability-এর difference explain করতে
- Appropriate places-এ controlled mutation retain করতে
Mutable Object কী?
Mutable object-এর state creation-এর পরে change করা যায়।
Example:
public class Course {
private String title;
public Course(
String title
) {
this.title =
title;
}
public void setTitle(
String title
) {
this.title =
title;
}
}
Usage:
Course course =
new Course(
"Java"
);
course.setTitle(
"Java Backend"
);
State changed:
Java
→
Java Backend
Immutable Object কী?
Immutable object creation-এর পরে তার state change করে না।
Example:
public final class CourseCode {
private final String value;
public CourseCode(
String value
) {
this.value =
value;
}
public String getValue() {
return value;
}
}
There is no:
setValue(...)
After creation:
CourseCode code =
new CourseCode(
"JAVA-OOP"
);
the represented value stays the same।
Why Immutability Is Useful
Immutable objects reduce an entire category of bugs।
If an object cannot change unexpectedly:
Reasoning becomes easier
Sharing becomes safer
Hash-based collections become safer
Validation becomes centralized
Concurrency becomes easier
You know:
Once valid, always valid
for that object's state।
Immutability Is Especially Good for Value Objects
Examples:
CourseCode
EmailAddress
Money
LessonId
Percentage
Coordinates
DateRange
These represent values rather than long-lived entities with evolving lifecycle।
Example:
CourseCode
should not suddenly mutate from:
JAVA-OOP
to:
BACKEND
If the value changes, conceptually it is a different course code।
final Fields
A common immutability building block:
private final String value;
final means:
The field reference can be assigned once
after construction/initialization।
Example:
public final class CourseCode {
private final String value;
public CourseCode(
String value
) {
this.value =
value;
}
}
After constructor:
this.value =
"OTHER";
is not allowed।
final Does Not Make Referenced Objects Immutable
Important:
private final List<String> lessons =
new ArrayList<>();
The reference cannot point to another list:
lessons =
new ArrayList<>();
but the existing list can still mutate:
lessons.add(
"Lesson 1"
);
So:
final reference
≠
immutable object
Example of the Difference
public final class Course {
private final List<String> lessons =
new ArrayList<>();
public void addLesson(
String lesson
) {
lessons.add(
lesson
);
}
}
The field is final, but Course is still mutable because its internal list changes।
Constructor Validation
Immutable object should be valid from the moment it is created।
Weak:
public CourseCode(
String value
) {
this.value =
value;
}
This permits:
new CourseCode(
null
);
or:
new CourseCode(
" "
);
Better:
public CourseCode(
String value
) {
if (
value == null
|| value.isBlank()
) {
throw new IllegalArgumentException(
"Course code is required."
);
}
this.value =
value.strip()
.toUpperCase();
}
Validate Before Assignment
Good:
String normalized =
value.strip()
.toUpperCase();
if (
!normalized.matches(
"[A-Z0-9-]+"
)
) {
throw new IllegalArgumentException(
"Invalid course code."
);
}
this.value =
normalized;
The object is never observable in a partially invalid state।
Immutable CourseCode
package io.liveklass.course;
import java.util.Locale;
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 normalized =
value.strip()
.toUpperCase(
Locale.ROOT
);
if (
!normalized.matches(
"[A-Z0-9-]+"
)
) {
throw new IllegalArgumentException(
"Course code contains unsupported characters."
);
}
this.value =
normalized;
}
public String getValue() {
return value;
}
@Override
public boolean equals(
Object other
) {
if (this == other) {
return true;
}
if (
!(other
instanceof CourseCode courseCode)
) {
return false;
}
return value.equals(
courseCode.value
);
}
@Override
public int hashCode() {
return Objects.hash(
value
);
}
@Override
public String toString() {
return value;
}
}
Why Make the Class final?
public final class CourseCode
prevents subclassing।
For immutable value objects, this can simplify reasoning because a subclass cannot secretly introduce mutable state or override behavior in surprising ways।
Strings Are Already Immutable
This field:
private final String value;
is safe because:
String
itself is immutable।
Returning:
public String getValue() {
return value;
}
does not expose mutable internal state।
Mutable Inputs Are More Dangerous
Consider:
public final class CoursePlan {
private final List<String> lessons;
public CoursePlan(
List<String> lessons
) {
this.lessons =
lessons;
}
}
Looks simple.
But caller still owns the same list reference।
Mutation Leak Through Constructor Input
List<String> lessons =
new ArrayList<>();
lessons.add(
"Variables"
);
CoursePlan plan =
new CoursePlan(
lessons
);
lessons.add(
"Loops"
);
Now CoursePlan also sees:
Loops
even though no method on CoursePlan changed anything।
This is a:
mutation leak
Defensive Copy
Instead of storing caller's mutable object directly:
this.lessons =
new ArrayList<>(
lessons
);
Now the object owns its own list।
Caller mutations no longer affect internal state।
Better: List.copyOf()
If you want an immutable snapshot:
this.lessons =
List.copyOf(
lessons
);
This creates an unmodifiable list representation।
Example
public final class CoursePlan {
private final List<String> lessons;
public CoursePlan(
List<String> lessons
) {
if (lessons == null) {
throw new IllegalArgumentException(
"Lessons are required."
);
}
this.lessons =
List.copyOf(
lessons
);
}
public List<String> getLessons() {
return lessons;
}
}
Because the stored list cannot be modified through its API, returning it is safe regarding list structure।
List.copyOf() Rejects null Elements
Important:
List.copyOf(...)
does not allow null elements।
Example:
List<String> values =
Arrays.asList(
"Java",
null
);
List.copyOf(
values
);
throws:
NullPointerException
This can be beneficial when null elements are invalid, but know the behavior।
Defensive Copy at Input Boundary
Suppose:
List<Lesson> lessons
is supplied by a caller।
A strong constructor:
public Course(
CourseCode code,
List<Lesson> lessons
) {
if (code == null) {
throw new IllegalArgumentException(
"Course code is required."
);
}
if (lessons == null) {
throw new IllegalArgumentException(
"Lessons are required."
);
}
this.code =
code;
this.lessons =
List.copyOf(
lessons
);
}
Now caller cannot mutate the list structure after construction।
But What If the Elements Are Mutable?
This is where things become more subtle।
Suppose:
public class Lesson {
private String title;
public void setTitle(
String title
) {
this.title =
title;
}
}
Then:
List<Lesson> lessons =
List.of(
lesson
);
this.lessons =
List.copyOf(
lessons
);
protects:
The list structure
but not:
The Lesson object itself
Caller can still mutate:
lesson.setTitle(
"Changed"
);
Shallow Immutability
List.copyOf() provides structural immutability of the list।
But if elements are mutable:
List cannot change
Elements can change
This is often called:
shallow immutability
Deep Immutability
Deep immutability means the entire reachable object graph is immutable।
Example:
CoursePlan
→ immutable List
→ immutable Lesson
→ immutable LessonId
Then no nested object can change either।
Deep Copying
One theoretical solution to mutable nested objects is deep copying।
Example conceptually:
List<Lesson> copies =
lessons.stream()
.map(
Lesson::copy
)
.toList();
But deep copying can become expensive and complicated।
A better design is often:
Use immutable nested value objects where possible
instead of constantly cloning everything।
Returning Internal Collections
Dangerous:
public List<Lesson> getLessons() {
return lessons;
}
if:
lessons
is a mutable ArrayList।
Caller can do:
course.getLessons()
.clear();
Now caller bypassed every business rule inside Course।
Example of Broken Encapsulation
public final class Course {
private final List<Lesson> lessons =
new ArrayList<>();
public List<Lesson> getLessons() {
return lessons;
}
}
Elsewhere:
course.getLessons()
.add(
lesson
);
The class has lost control over:
Duplicate checks
Maximum lesson count
Ordering rules
Publication restrictions
Return a Snapshot
Safer:
public List<Lesson> getLessons() {
return List.copyOf(
lessons
);
}
Now caller gets a snapshot view of list structure।
Returning a New ArrayList
Another option:
public List<Lesson> getLessons() {
return new ArrayList<>(
lessons
);
}
Caller can mutate the returned list:
result.clear();
but internal list remains unaffected।
This is also defensive copying।
List.copyOf() vs new ArrayList<>()
List.copyOf()
Returns an unmodifiable snapshot।
Caller cannot modify the returned list।
new ArrayList<>(...)
Returns a mutable copy।
Caller may modify its own copy।
Both protect internal collection ownership।
Choose based on API semantics।
Collections.unmodifiableList()
Another API:
Collections.unmodifiableList(
lessons
);
But be careful:
unmodifiableList()
wraps the existing list।
If the internal original list changes later, the returned view reflects those changes।
Example
List<String> internal =
new ArrayList<>();
List<String> view =
Collections.unmodifiableList(
internal
);
internal.add(
"Java"
);
Now:
view
also contains:
Java
It is unmodifiable through view, but it is not necessarily a snapshot।
Snapshot vs Unmodifiable View
Important difference:
List.copyOf(
internal
)
creates an immutable snapshot-like copy.
Collections.unmodifiableList(
internal
)
creates an unmodifiable view backed by the same underlying list।
For public getters, snapshots are often easier to reason about।
Controlled Mutation
Not every class should be immutable।
An entity with lifecycle may legitimately change।
Example:
Course
may transition:
DRAFT
→ REVIEW
→ PUBLISHED
That is meaningful domain behavior।
The goal is not:
Never mutate anything
The goal is:
Only allow valid, controlled mutation
Weak Mutable Entity
public class Course {
public String title;
public CourseStatus status;
public List<Lesson> lessons;
}
Anything can mutate anything at any time।
Better Controlled Entity
public final class Course {
private final CourseCode code;
private final List<Lesson> lessons;
private String title;
private CourseStatus status;
public void changeTitle(
String newTitle
) {
// validate
}
public void publish() {
// enforce state transition
}
public void addLesson(
Lesson lesson
) {
// enforce rules
}
}
Mutation exists, but class owns the rules।
Protecting Invariants
An invariant is a condition that must always remain true for a valid object।
Examples:
Course code is never blank
Price is never negative
Published course must have at least one lesson
Lesson IDs must be unique
Maximum lesson count is 50
Class design should make it difficult to violate these rules।
Example: Unique Lessons
Weak:
course.getLessons()
.add(
lesson
);
No duplicate check।
Better:
public void addLesson(
Lesson lesson
) {
if (lesson == null) {
throw new IllegalArgumentException(
"Lesson is required."
);
}
boolean duplicate =
lessons.stream()
.anyMatch(
current ->
current.getId()
.equals(
lesson.getId()
)
);
if (duplicate) {
throw new IllegalArgumentException(
"Lesson already exists."
);
}
lessons.add(
lesson
);
}
The class protects its own invariant।
Getter Does Not Need to Start with get
JavaBean-style:
getLessons()
is common।
But domain-focused APIs can also use:
lessons()
depending on project conventions।
The important point is not naming style।
The important point is:
Do not expose mutable ownership accidentally
Arrays Are Mutable Too
Consider:
public final class Document {
private final byte[] content;
public Document(
byte[] content
) {
this.content =
content;
}
}
Caller can mutate the array later।
Defensive Array Copy on Input
this.content =
content.clone();
or:
this.content =
Arrays.copyOf(
content,
content.length
);
Defensive Array Copy on Output
Dangerous:
public byte[] getContent() {
return content;
}
Caller can mutate internal state।
Better:
public byte[] getContent() {
return content.clone();
}
Defensive Copy Both Ways
For mutable inputs, usually protect both boundaries:
Constructor input
Getter output
Example:
public final class BinaryDocument {
private final byte[] content;
public BinaryDocument(
byte[] content
) {
if (content == null) {
throw new IllegalArgumentException(
"Content is required."
);
}
this.content =
content.clone();
}
public byte[] getContent() {
return content.clone();
}
}
Dates and Time Types
Modern Java types from:
java.time
such as:
LocalDate
LocalDateTime
Instant
Duration
are immutable।
This makes them much safer than older mutable date APIs।
Example:
private final Instant createdAt;
can safely be returned directly।
Reassignment vs Mutation
Consider:
private final List<String> lessons;
final prevents:
lessons =
anotherList;
but not:
lessons.add(
"Java"
);
Always ask:
Is the reference final?
Is the referenced object itself mutable?
These are separate questions।
Mutable Fields Can Still Be Encapsulated Safely
Example:
private final List<Lesson> lessons =
new ArrayList<>();
This is perfectly reasonable if:
- Field is private
- Caller cannot mutate it directly
- All changes go through domain methods
- Getters return snapshots
Mutable internal state is not inherently bad।
Uncontrolled mutation is the problem।
Example: Course Owning Its Collection
public final class Course {
private final CourseCode code;
private final List<Lesson> lessons =
new ArrayList<>();
public Course(
CourseCode code
) {
if (code == null) {
throw new IllegalArgumentException(
"Course code is required."
);
}
this.code =
code;
}
public void addLesson(
Lesson lesson
) {
if (lesson == null) {
throw new IllegalArgumentException(
"Lesson is required."
);
}
if (
lessons.contains(
lesson
)
) {
throw new IllegalArgumentException(
"Lesson already exists."
);
}
lessons.add(
lesson
);
}
public List<Lesson> getLessons() {
return List.copyOf(
lessons
);
}
}
This class is mutable, but mutation is controlled।
Constructor Should Not Store External Collections Directly
Weak:
this.lessons =
lessons;
Better:
this.lessons =
new ArrayList<>(
lessons
);
if the entity needs to mutate its own list later।
Why not:
List.copyOf()
here?
Because if Course needs internal:
addLesson()
the internal collection must remain mutable।
So use:
new ArrayList<>(lessons)
for private ownership।
Example: Mutable Internally, Safe Externally
public Course(
List<Lesson> initialLessons
) {
this.lessons =
new ArrayList<>(
initialLessons
);
}
public void addLesson(
Lesson lesson
) {
lessons.add(
lesson
);
}
public List<Lesson> getLessons() {
return List.copyOf(
lessons
);
}
Pattern:
Input → defensive mutable copy
Internal → controlled mutation
Output → immutable snapshot
This is extremely useful in domain entities।
Map Defensive Copy
Same principle:
private final Map<String, String> metadata;
Immutable snapshot:
this.metadata =
Map.copyOf(
metadata
);
Getter:
public Map<String, String> getMetadata() {
return metadata;
}
provided nested values are safe।
Set Defensive Copy
this.tags =
Set.copyOf(
tags
);
Again:
Collection structure is immutable
but contained mutable objects may still change।
Defensive Programming with Nulls
A defensive class defines null policy explicitly।
Weak:
this.title =
title.trim();
If title == null:
NullPointerException
Better domain error:
if (
title == null
|| title.isBlank()
) {
throw new IllegalArgumentException(
"Course title is required."
);
}
Now failure communicates intent।
Objects.requireNonNull()
For simple null checks:
this.code =
Objects.requireNonNull(
code,
"Course code is required."
);
This throws:
NullPointerException
with the given message।
This is fine when null itself represents a programming error।
For domain argument validation, some teams prefer:
IllegalArgumentException
Consistency matters।
Fail Fast
A defensive object should reject invalid input early।
Weak:
Course course =
new Course(
"",
-100
);
Then failure appears much later during:
repository.save(
course
);
Better:
Object creation fails immediately
This keeps invalid state from flowing through the application।
Normalize Once
If a value has a canonical representation:
new CourseCode(
" java-oop "
)
normalize in constructor:
JAVA-OOP
Then every consumer sees the same value।
Do not repeatedly normalize throughout the application।
Defensive Programming Is Not "Validate Everything Everywhere"
Bad design:
Controller validates course code
Service validates same syntax
Repository validates same syntax
Formatter validates same syntax
If CourseCode guarantees its own validity, downstream code can rely on that invariant।
Defensive programming should create strong boundaries, not repetitive noise।
Trust Strong Types
If method receives:
CourseCode courseCode
it should not re-check:
Is the course code blank?
Does it contain spaces?
Those should already be impossible.
It may still check:
courseCode == null
depending on the API contract।
Immutable Return Values Make APIs Easier
Compare:
List<Lesson> getLessons()
where caller must wonder:
Can I mutate this?
Will it affect the course?
Is this live?
With documented immutable snapshot semantics:
This is a read-only snapshot
the contract is simpler।
Immutability and Hash-Based Collections
Suppose an object is used as a key:
Map<CourseCode, Course>
If CourseCode could mutate after insertion, its:
hashCode()
might change।
Then map lookup can break।
Immutable value objects are ideal as:
Map keys
Set elements
Example of Dangerous Mutable Key
class MutableCode {
String value;
@Override
public int hashCode() {
return value.hashCode();
}
}
Insert:
MutableCode code =
new MutableCode();
code.value =
"JAVA";
map.put(
code,
course
);
Then mutate:
code.value =
"BACKEND";
The object may now be stored under a hash bucket based on the old value but report a new hash code।
Lookups become unreliable।
Prefer Immutable Keys
public final class CourseCode {
private final String value;
// validation, equals, hashCode
}
Once inserted into:
Map
its equality and hash remain stable।
Immutable Does Not Mean Thread-Safe Everything
Immutable objects are generally safe to share because their state does not change।
But an object containing:
private final SomeMutableObject value;
is not truly immutable just because the reference is final।
Thread safety depends on the whole reachable state and surrounding operations।
final Class Is Not Enough
This:
public final class CoursePlan {
private final List<Lesson> lessons;
public CoursePlan(
List<Lesson> lessons
) {
this.lessons =
lessons;
}
}
is still not immutable.
Caller can mutate:
lessons
The class being final prevents inheritance, not external mutation of referenced objects।
Immutable Object Checklist
A practically immutable class often has:
- Class is
finalor safely designed for inheritance - Fields are
private - State fields are
final - Constructor validates inputs
- Mutable inputs are copied
- Mutable internals are not exposed
- Nested objects are immutable or safely copied
- No mutating methods
equals()andhashCode()use stable state
Controlled Mutable Entity Checklist
A well-encapsulated mutable entity often has:
- Private fields
- No public mutable collection exposure
- Mutation through meaningful methods
- Methods enforce invariants
- Defensive copies at boundaries
- Snapshot getters
- Invalid transitions rejected
- Callers cannot bypass business rules
Example: Lesson
public final class Lesson {
private final long id;
private final String title;
public Lesson(
long id,
String title
) {
if (id <= 0) {
throw new IllegalArgumentException(
"Lesson id must be positive."
);
}
if (
title == null
|| title.isBlank()
) {
throw new IllegalArgumentException(
"Lesson title is required."
);
}
this.id = id;
this.title = title.strip();
}
public long getId() {
return id;
}
public String getTitle() {
return title;
}
}
This is immutable।
Example: Controlled Mutable Course
public final class Course {
private static final int MAX_LESSONS =
50;
private final CourseCode code;
private final List<Lesson> lessons;
private String title;
private CourseStatus status;
public Course(
CourseCode code,
String title,
List<Lesson> initialLessons
) {
if (code == null) {
throw new IllegalArgumentException(
"Course code is required."
);
}
if (
title == null
|| title.isBlank()
) {
throw new IllegalArgumentException(
"Course title is required."
);
}
if (initialLessons == null) {
throw new IllegalArgumentException(
"Initial lessons are required."
);
}
if (
initialLessons.size()
> MAX_LESSONS
) {
throw new IllegalArgumentException(
"Course cannot have more than "
+ MAX_LESSONS
+ " lessons."
);
}
this.code =
code;
this.title =
title.strip();
this.status =
CourseStatus.DRAFT;
this.lessons =
new ArrayList<>(
initialLessons
);
ensureUniqueLessonIds();
}
public void changeTitle(
String newTitle
) {
if (
newTitle == null
|| newTitle.isBlank()
) {
throw new IllegalArgumentException(
"Course title is required."
);
}
if (
status == CourseStatus.PUBLISHED
) {
throw new IllegalStateException(
"Published course title cannot be changed."
);
}
title =
newTitle.strip();
}
public void addLesson(
Lesson lesson
) {
if (lesson == null) {
throw new IllegalArgumentException(
"Lesson is required."
);
}
if (
lessons.size()
>= MAX_LESSONS
) {
throw new IllegalStateException(
"Course has reached the lesson limit."
);
}
boolean duplicate =
lessons.stream()
.anyMatch(
current ->
current.getId()
== lesson.getId()
);
if (duplicate) {
throw new IllegalArgumentException(
"Lesson id already exists."
);
}
lessons.add(
lesson
);
}
public void publish() {
if (
lessons.isEmpty()
) {
throw new IllegalStateException(
"Course must have at least one lesson before publishing."
);
}
status =
CourseStatus.PUBLISHED;
}
public CourseCode getCode() {
return code;
}
public String getTitle() {
return title;
}
public CourseStatus getStatus() {
return status;
}
public List<Lesson> getLessons() {
return List.copyOf(
lessons
);
}
private void ensureUniqueLessonIds() {
long uniqueCount =
lessons.stream()
.map(
Lesson::getId
)
.distinct()
.count();
if (
uniqueCount
!= lessons.size()
) {
throw new IllegalArgumentException(
"Initial lessons contain duplicate ids."
);
}
}
}
What This Course Class Protects
Caller cannot:
course.getLessons()
.clear();
Cannot add duplicate lesson through public API।
Cannot exceed lesson limit।
Cannot publish empty course।
Cannot change published title।
The object owns its invariants।
Common Mistakes
Thinking final Makes Everything Immutable
It only prevents field reassignment.
Storing Caller-Owned Mutable Collections Directly
Caller can mutate internal state indirectly।
Returning Mutable Internal Collections
Breaks encapsulation।
Using Collections.unmodifiableList() Assuming It Is a Snapshot
It may still reflect underlying mutations।
Copying the Collection but Ignoring Mutable Elements
Structural copy does not deep-copy elements।
Making Every Domain Object Immutable
Some entities legitimately have lifecycle and behavior।
Using Public Setters for Every Field
Allows invalid combinations and bypasses domain rules।
Validating Too Late
Invalid objects spread through the application।
Revalidating Strong Types Everywhere
Creates duplication without improving correctness।
Using Mutable Objects as Map Keys
Changes to equality/hash state can break collection behavior।
Practice Exercises
Exercise 1: Fix Mutation Leak
Given:
public final class CoursePlan {
private final List<String> lessons;
public CoursePlan(
List<String> lessons
) {
this.lessons =
lessons;
}
public List<String> getLessons() {
return lessons;
}
}
Make it immutable।
Exercise 2: Immutable Money
Create:
Money
with:
amountInCents
currency
Requirements:
- Fields final
- Amount cannot be negative
- Currency cannot be blank
- No setters
- Implement
equals()andhashCode()
Exercise 3: Defensive Array Copy
Create:
BinaryContent
that accepts:
byte[]
and prevents mutation both through constructor input and getter output।
Exercise 4: Controlled Course Mutation
Create a Course that:
- Owns an internal
ArrayList<Lesson> - Rejects duplicate lesson IDs
- Returns immutable snapshots
- Allows lessons to be added only while status is
DRAFT
Exercise 5: Identify Shallow Immutability
Given:
private final List<MutableLesson> lessons =
List.copyOf(
input
);
Explain what is immutable and what is still mutable।
Predict the Result
Question 1
final List<String> names =
new ArrayList<>();
names.add(
"Sakib"
);
Does this compile?
Answer
Yes।
final prevents reassigning names, not mutating the referenced list।
Question 2
List<String> original =
new ArrayList<>();
original.add(
"Java"
);
List<String> copy =
List.copyOf(
original
);
original.add(
"Spring"
);
Does copy now contain "Spring"?
Answer
No।
List.copyOf() created a separate unmodifiable representation of the original contents at copy time।
Question 3
List<Lesson> snapshot =
course.getLessons();
snapshot.add(
lesson
);
if getLessons() returns:
List.copyOf(
lessons
)
what happens?
Answer
An:
UnsupportedOperationException
is thrown।
Question 4
Does List.copyOf() make each mutable element immutable?
Answer
No।
It protects the list structure, not mutable objects referenced by the list।
Question 5
Why is an immutable CourseCode safer as a Map key?
Answer
Its equality and hash state remain stable after insertion।
Knowledge Check
Question 1
What is an immutable object?
Question 2
What does final guarantee for a field?
Question 3
Does a final List become immutable?
Question 4
What is defensive copying?
Question 5
Why copy mutable constructor arguments?
Question 6
Why should internal mutable collections not be returned directly?
Question 7
What does List.copyOf() provide?
Question 8
What is the difference between an immutable snapshot and an unmodifiable view?
Question 9
What is shallow immutability?
Question 10
What is deep immutability?
Question 11
Why are value objects often good candidates for immutability?
Question 12
Should every entity be immutable?
Question 13
What is an invariant?
Question 14
Why are immutable keys safer in hash-based collections?
Question 15
What is the goal of defensive programming?
Knowledge Check Answers
Answer 1
An object whose observable state does not change after creation।
Answer 2
The field reference or primitive value can be assigned only once during initialization/construction।
Answer 3
No. The list object may still be mutable।
Answer 4
Creating an owned copy of mutable input or output so external code cannot mutate internal state indirectly।
Answer 5
Otherwise caller changes may silently change the object's internal state।
Answer 6
Caller could bypass the class's validation and mutation rules।
Answer 7
An unmodifiable copy of the provided collection's current contents।
Answer 8
A snapshot is independent of later source mutations; an unmodifiable view may still reflect mutations to its backing collection।
Answer 9
The outer object's structure is protected, but nested referenced objects may still mutate।
Answer 10
The object and all relevant objects reachable from it are immutable।
Answer 11
Their identity is defined by stable values and they usually should not change after construction।
Answer 12
No. Entities with meaningful lifecycle changes can be mutable, but mutation should be controlled।
Answer 13
A condition that must always remain true for an object to be valid।
Answer 14
Their equals() and hashCode() behavior remains stable after insertion।
Answer 15
To make invalid state, accidental mutation, and misuse harder while keeping failures close to their source।
Lesson Summary
এই lesson-এ আমরা শিখেছি:
- Mutable objects creation-এর পরে state change করতে পারে
- Immutable objects creation-এর পরে observable state change করে না
finalfield reassignment prevent করে, referenced object mutation নয়- Constructor validation objectকে শুরু থেকেই valid রাখতে সাহায্য করে
- Value objects immutability-এর strong candidates
- Mutable constructor inputs directly store করা mutation leak তৈরি করতে পারে
- Defensive copying object ownership protect করে
List.copyOf()unmodifiable snapshot-style copy তৈরি করে- Returning internal mutable collections encapsulation break করে
- Snapshot getters internal state protect করে
Collections.unmodifiableList()backed view হতে পারে, independent snapshot নয়- Immutable collection mutable elementsকে automatically immutable করে না
- Arrays mutable এবং input/output দুই দিকেই defensive copy প্রয়োজন হতে পারে
- Shallow এবং deep immutability আলাদা concepts
- Entities mutable হতে পারে যদি mutation controlled এবং meaningful হয়
- Public setters-এর বদলে intention-revealing methods invariants better protect করে
- Classes should protect their own invariants
- Strong types reduce repetitive validation
- Immutable objects hash-based collections-এর keys হিসেবে safer
- Defensive programming-এর লক্ষ্য everything validate করা নয়; strong boundaries তৈরি করা
- A useful pattern for entities is:
Defensive input copy
→ private controlled mutation
→ immutable output snapshot
Next Lesson
পরবর্তী lesson:
Equality, Hashing, and Object Contracts
আমরা শিখব:
- Reference equality vs logical equality
==vsequals()- The
equals()contract - The
hashCode()contract - Why
HashSetandHashMapdepend on both - Implementing value-object equality
- Mutable equality fields-এর danger
Objects.equals()Objects.hash()- Designing useful
toString() - Common equality bugs