Professional Java Practices
Records and Modern Data Models
You are viewing a free preview lesson.
Lesson Overview
Java-তে অনেক class আছে যেগুলোর primary কাজ শুধু data hold করা।
Example:
Course summary
Lesson response
Coordinates
Money snapshot
Search result
Configuration
Command input
Traditional class লিখলে অনেক boilerplate লাগে:
private final fields
constructor
getters
equals()
hashCode()
toString()
Modern Java এই ধরনের data-focused types-এর জন্য provide করে:
record
Example:
public record CourseSummary(
String code,
String title
) {
}
এই ছোট declaration থেকেই Java automatically generate করে:
- Private final fields
- Constructor
- Accessor methods
equals()hashCode()toString()
Records powerful, but তারা every class-এর replacement নয়।
এই lesson-এ আমরা শিখব:
recordকী- Record components
- Generated constructor
- Accessor methods
equals(),hashCode(),toString()- Compact constructor
- Validation
- Normalization
- Additional methods
- Static members
- Records with collections
- Defensive copying
- Record vs normal class
- Record vs entity
- Records as DTOs and value objects
- Common record mistakes
Learning Objectives
এই lesson শেষে আপনি পারবেন:
- Java record explain করতে
- Simple record declare করতে
- Generated methods identify করতে
- Compact constructor ব্যবহার করতে
- Record input validate এবং normalize করতে
- Collection components defensively copy করতে
- Record-এর value equality explain করতে
- Record এবং normal class-এর মধ্যে choose করতে
- Record-কে mutable entity replacement হিসেবে ভুলভাবে ব্যবহার করা avoid করতে
What Is a Record?
A record হলো special Java type designed primarily for:
Immutable data representation
Example:
public record CourseSummary(
String code,
String title
) {
}
The values inside parentheses are called:
record components
Here:
code
title
are components।
Traditional Class Equivalent
Without a record, you might write:
public final class CourseSummary {
private final String code;
private final String title;
public CourseSummary(
String code,
String title
) {
this.code = code;
this.title = title;
}
public String code() {
return code;
}
public String title() {
return title;
}
@Override
public boolean equals(
Object other
) {
// implementation
}
@Override
public int hashCode() {
// implementation
}
@Override
public String toString() {
// implementation
}
}
A record removes most of this boilerplate।
Basic Record
public record CourseSummary(
String code,
String title
) {
}
Usage:
CourseSummary summary =
new CourseSummary(
"JAVA-OOP",
"Java and OOP Foundation"
);
Generated Accessors
Record accessor names are the same as component names।
Use:
summary.code();
summary.title();
Not:
summary.getCode();
summary.getTitle();
unless you explicitly add such methods yourself।
Record Components Become Final State
A record is designed so its component fields cannot be reassigned after construction।
Conceptually:
private final String code;
private final String title;
There are no generated setters।
Generated Constructor
For:
public record CourseSummary(
String code,
String title
) {
}
Java provides a canonical constructor conceptually similar to:
public CourseSummary(
String code,
String title
) {
this.code = code;
this.title = title;
}
Generated equals()
Records automatically provide value-based equality across their components।
Example:
CourseSummary first =
new CourseSummary(
"JAVA",
"Java"
);
CourseSummary second =
new CourseSummary(
"JAVA",
"Java"
);
System.out.println(
first.equals(
second
)
);
Output:
true
Generated hashCode()
Because records generate hashCode() consistently with equals(), they work naturally with:
HashSet
HashMap
Example:
Set<CourseSummary> summaries =
new HashSet<>();
summaries.add(
new CourseSummary(
"JAVA",
"Java"
)
);
summaries.add(
new CourseSummary(
"JAVA",
"Java"
)
);
System.out.println(
summaries.size()
);
Output:
1
Generated toString()
Example:
System.out.println(
summary
);
Possible output:
CourseSummary[code=JAVA-OOP, title=Java and OOP Foundation]
This is much more useful than default Object.toString()।
Records Are Value-Oriented
A record's equality is based on all record components।
For:
public record Money(
long amountInCents,
String currency
) {
}
These are equal:
new Money(
1000,
"EUR"
)
and:
new Money(
1000,
"EUR"
)
But:
new Money(
1000,
"USD"
)
is different।
Validation in a Record
A basic record accepts whatever values are passed।
Example:
new CourseSummary(
null,
""
);
This compiles unless you add validation।
Use a:
compact constructor
Compact Constructor
public record CourseSummary(
String code,
String title
) {
public CourseSummary {
if (
code == null
|| code.isBlank()
) {
throw new IllegalArgumentException(
"Course code is required."
);
}
if (
title == null
|| title.isBlank()
) {
throw new IllegalArgumentException(
"Course title is required."
);
}
}
}
Notice:
public CourseSummary {
There is no parameter list।
The record components are automatically available as constructor parameters।
Why Is It Called Compact Constructor?
The full canonical constructor would look conceptually like:
public CourseSummary(
String code,
String title
) {
this.code = code;
this.title = title;
}
Compact constructor lets you focus on:
Validation
Normalization
while Java still assigns the record components automatically after the constructor body।
Normalize Values
You can reassign constructor parameters inside the compact constructor before Java stores them।
Example:
public record CourseCode(
String value
) {
public CourseCode {
if (
value == null
|| value.isBlank()
) {
throw new IllegalArgumentException(
"Course code is required."
);
}
value =
value.strip()
.toUpperCase();
}
}
Usage:
CourseCode code =
new CourseCode(
" java-oop "
);
System.out.println(
code.value()
);
Output:
JAVA-OOP
Validate Normalized Value
Better:
public record CourseCode(
String value
) {
public CourseCode {
if (
value == null
|| value.isBlank()
) {
throw new IllegalArgumentException(
"Course code is required."
);
}
value =
value.strip()
.toUpperCase();
if (
!value.matches(
"[A-Z0-9-]+"
)
) {
throw new IllegalArgumentException(
"Course code contains unsupported characters."
);
}
}
}
Now every CourseCode is valid and normalized।
Record Accessors Are Methods
For:
public record CourseCode(
String value
) {
}
access:
code.value();
not:
code.value
because the field itself is private।
You Can Add Methods
Records are not limited to storing data।
Example:
public record Money(
long amountInCents,
String currency
) {
public boolean isFree() {
return amountInCents
== 0;
}
}
Usage:
Money price =
new Money(
0,
"EUR"
);
System.out.println(
price.isFree()
);
Records Can Have Static Members
public record Money(
long amountInCents,
String currency
) {
public static final String DEFAULT_CURRENCY =
"EUR";
public static Money free() {
return new Money(
0,
DEFAULT_CURRENCY
);
}
}
Records Cannot Have Additional Instance Fields
You cannot add arbitrary extra mutable instance state outside the record components।
This is intentionally restrictive।
The record header should describe the complete state representation।
Why This Restriction Is Useful
A record says:
These components define this value.
Generated:
equals
hashCode
toString
all use those components।
Hidden extra instance state would make that model confusing।
Records Are Implicitly Final
You cannot extend a record।
This:
class PremiumSummary
extends CourseSummary {
}
is not allowed।
Records are effectively final।
This simplifies equality semantics।
Records Can Implement Interfaces
Example:
public interface Identified {
String id();
}
Record:
public record LessonSummary(
String id,
String title
) implements Identified {
}
This is valid।
Records Cannot Extend Ordinary Classes
A record cannot extend your own base class।
It already has its special inheritance relationship defined by Java।
If inheritance is central to the design, a record may not be the right type।
Records with Collections
Consider:
public record CoursePlan(
String courseCode,
List<String> lessons
) {
}
Looks immutable.
But is it truly safe?
Not necessarily।
Mutable Collection Leak
List<String> lessons =
new ArrayList<>();
lessons.add(
"Variables"
);
CoursePlan plan =
new CoursePlan(
"JAVA",
lessons
);
lessons.add(
"Loops"
);
If record stores the same mutable list reference, plan.lessons() now also reflects "Loops"।
Record components are final references, but referenced objects can still mutate।
Defensive Copy in Record Constructor
public record CoursePlan(
String courseCode,
List<String> lessons
) {
public CoursePlan {
if (
courseCode == null
|| courseCode.isBlank()
) {
throw new IllegalArgumentException(
"Course code is required."
);
}
if (lessons == null) {
throw new IllegalArgumentException(
"Lessons are required."
);
}
courseCode =
courseCode.strip()
.toUpperCase();
lessons =
List.copyOf(
lessons
);
}
}
Now the record owns an unmodifiable snapshot of the list structure।
Record Does Not Automatically Deep-Copy Components
If the list contains mutable objects:
List<MutableLesson>
then:
List.copyOf(...)
protects the list structure only।
The elements can still mutate।
Records do not automatically provide deep immutability।
Record with Immutable Elements
Better:
public record LessonSummary(
long id,
String title
) {
}
Then:
public record CoursePlan(
CourseCode courseCode,
List<LessonSummary> lessons
) {
public CoursePlan {
lessons =
List.copyOf(
lessons
);
}
}
Now the object graph is much easier to reason about because nested elements are immutable too।
Records and Arrays
Array components remain mutable।
Weak:
public record BinaryContent(
byte[] data
) {
}
Caller can mutate:
content.data()[0] =
99;
This changes the array stored by the record।
Defensive Array Record
You can copy on input:
public record BinaryContent(
byte[] data
) {
public BinaryContent {
if (data == null) {
throw new IllegalArgumentException(
"Data is required."
);
}
data =
data.clone();
}
}
But there is another problem।
Generated accessor:
data()
returns the internal array directly।
Override an Accessor
You can override a record accessor:
@Override
public byte[] data() {
return data.clone();
}
Complete:
public record BinaryContent(
byte[] data
) {
public BinaryContent {
if (data == null) {
throw new IllegalArgumentException(
"Data is required."
);
}
data =
data.clone();
}
@Override
public byte[] data() {
return data.clone();
}
}
Now both input and output boundaries are protected।
But Arrays Affect Generated Equality
There is an important issue।
A record containing:
byte[]
uses the component's normal equals() semantics।
Arrays use reference equality for equals()।
So:
new BinaryContent(
new byte[] {1, 2}
)
and another record with a separate equal-content array may not compare as logically equal by content unless you override equality yourself।
This is one reason mutable arrays are often awkward record components।
Prefer Better Value Types Where Possible
Instead of storing mutable arrays directly in a value record, consider whether your design can use:
String
List<Byte>
Immutable wrapper
Domain-specific immutable type
depending on the problem।
Do not force a record when its component semantics do not match value equality naturally।
Records and equals()
Generated record equality includes every component।
Example:
public record CourseSummary(
String code,
String title,
CourseStatus status
) {
}
If status changes conceptually, you create a different record:
new CourseSummary(
"JAVA",
"Java",
CourseStatus.DRAFT
)
is not equal to:
new CourseSummary(
"JAVA",
"Java",
CourseStatus.PUBLISHED
)
This is appropriate for a snapshot representation।
Records Are Excellent for Snapshots
Imagine:
Course
is a mutable domain entity।
You want to expose a read-only summary:
public record CourseSummary(
String code,
String title,
CourseStatus status
) {
}
The summary describes:
Course state at a point in time
If the course later changes, create another summary।
Entity vs Record
Suppose a Course has lifecycle:
DRAFT
→ REVIEW
→ PUBLISHED
→ ARCHIVED
and behavior:
course.addLesson(...)
course.publish()
course.archive()
This is usually better modeled as a normal class।
Why?
Because the object owns:
Identity
Behavior
Controlled state transitions
Invariants
A record is better when the primary purpose is:
Represent a value or snapshot
Do Not Replace Every Domain Class with a Record
Weak thinking:
Records are modern
Therefore every class should be a record
No।
Choose based on semantics।
Good Record Candidates
Common good candidates:
CourseCode
Money
DateRange
Coordinates
CourseSummary
SearchResult
ValidationError
FieldError
Configuration
Command input
Query result
Normal Class Candidates
Common normal-class candidates:
Course
Enrollment
ShoppingCart
BankAccount
Order
Subscription
when they own lifecycle and meaningful mutation।
Record as a Value Object
Our earlier CourseCode class can be greatly simplified।
Traditional:
public final class CourseCode {
private final String value;
// constructor
// getter
// equals
// hashCode
// toString
}
Record:
public record CourseCode(
String value
) {
public CourseCode {
if (
value == null
|| value.isBlank()
) {
throw new IllegalArgumentException(
"Course code is required."
);
}
value =
value.strip()
.toUpperCase();
}
}
Do You Need to Override toString()?
Generated:
CourseCode[value=JAVA-OOP]
Maybe you prefer:
JAVA-OOP
Then override:
@Override
public String toString() {
return value;
}
Complete:
public record CourseCode(
String value
) {
public CourseCode {
if (
value == null
|| value.isBlank()
) {
throw new IllegalArgumentException(
"Course code is required."
);
}
value =
value.strip()
.toUpperCase();
}
@Override
public String toString() {
return value;
}
}
Record as DTO
DTO means:
Data Transfer Object
It carries data between boundaries।
Example:
public record CreateCourseRequest(
String code,
String title,
long priceInPaisa
) {
}
This is often a natural record because its main purpose is holding input data।
Validation Responsibility Still Matters
Just because a request is a record does not mean all domain validation belongs there।
Example:
CreateCourseRequest
may validate structural things:
title not null
But business rules such as:
Instructor subscription allows only 10 courses
do not belong in the record constructor।
That requires application/domain context।
Record as Response
Example:
public record CourseResponse(
String code,
String title,
String status
) {
}
This cleanly communicates:
A read-only output shape
Record as Validation Error
public record ValidationError(
String field,
String message
) {
}
Great candidate because it simply represents a value pair।
Record as Map Key
Immutable records with stable components can make excellent map keys।
Example:
public record EnrollmentKey(
long userId,
CourseCode courseCode
) {
}
Usage:
Map<EnrollmentKey, Enrollment> enrollments =
new HashMap<>();
Generated equality and hashing include both components।
Validate Primitive Values Too
Example:
public record LessonId(
long value
) {
public LessonId {
if (value <= 0) {
throw new IllegalArgumentException(
"Lesson id must be positive."
);
}
}
}
This creates a strong type instead of passing arbitrary:
long
everywhere।
Record with Derived Behavior
public record Price(
long amountInCents
) {
public Price {
if (amountInCents < 0) {
throw new IllegalArgumentException(
"Price cannot be negative."
);
}
}
public boolean isFree() {
return amountInCents
== 0;
}
}
A record can contain useful domain behavior।
But Keep It Cohesive
If a record starts accumulating:
20 mutable-looking operations
5 service dependencies
repository calls
network calls
complex lifecycle transitions
it is probably no longer a simple value representation।
A normal class may better communicate the design।
Records and Setters
You cannot sensibly add:
setTitle(...)
that changes the record component because components are final।
If you want a changed record, create a new value।
Example:
public CourseSummary withTitle(
String newTitle
) {
return new CourseSummary(
code,
newTitle
);
}
Functional Update Style
Example:
public record CourseSummary(
String code,
String title
) {
public CourseSummary withTitle(
String newTitle
) {
return new CourseSummary(
code,
newTitle
);
}
}
Usage:
CourseSummary original =
new CourseSummary(
"JAVA",
"Java"
);
CourseSummary updated =
original.withTitle(
"Java Foundation"
);
original stays unchanged।
Record Copy Is Explicit
Records do not provide a universal:
copy()
method like some other languages।
You create a new record:
new CourseSummary(
old.code(),
newTitle
);
You may add helper methods if they meaningfully improve readability।
Records and Serialization
Libraries such as JSON mappers often support records well in modern Java ecosystems।
But do not confuse:
Record
with:
Serialization format
A record is still just a Java type।
JSON, database rows, files, and network protocols remain separate concerns।
Records and Persistence Entities
Using records directly as database entities can be possible with some tools, but framework requirements vary।
Do not choose a record only because:
It has fewer lines of code
Choose according to lifecycle, persistence tool, and domain semantics।
Records and Framework Boundaries
Records are especially useful at boundaries such as:
Request DTO
Response DTO
Query result
Configuration
Message payload
Application command
because boundary data is often:
Created once
Read many times
Not mutated
Constructor Normalization and Equality
This is powerful:
public record EmailAddress(
String value
) {
public EmailAddress {
if (
value == null
|| value.isBlank()
) {
throw new IllegalArgumentException(
"Email is required."
);
}
value =
value.strip()
.toLowerCase();
}
}
Then:
new EmailAddress(
"SAKIB@EXAMPLE.COM"
)
and:
new EmailAddress(
"sakib@example.com"
)
become equal according to the normalization policy।
But normalization rules must match the actual domain।
Do not normalize blindly।
Record Component Order Matters
Given:
public record Money(
long amount,
String currency
) {
}
constructor order is:
new Money(
amount,
currency
);
With several same-type components:
public record PersonName(
String firstName,
String lastName
) {
}
this can compile accidentally:
new PersonName(
lastName,
firstName
);
because both are String।
Strong types can help if mistakes would be costly।
Example with Strong Types
public record FirstName(
String value
) {
}
public record LastName(
String value
) {
}
public record PersonName(
FirstName firstName,
LastName lastName
) {
}
Now accidentally swapping them no longer compiles।
Do not introduce tiny wrapper types everywhere, but use them when semantic safety is valuable।
Records Reduce Boilerplate, Not Design Responsibility
This:
public record Course(
String code,
String title,
List<Lesson> lessons,
CourseStatus status
) {
}
may compile in one line।
But you still need to ask:
Should lessons be mutable?
Can status be arbitrary?
Can an empty course be published?
Who owns course transitions?
Is this an entity or snapshot?
Less code does not mean fewer design decisions।
Example: Good Record Design
public record CourseSummary(
CourseCode code,
String title,
CourseStatus status
) {
public CourseSummary {
if (code == null) {
throw new IllegalArgumentException(
"Course code is required."
);
}
if (
title == null
|| title.isBlank()
) {
throw new IllegalArgumentException(
"Course title is required."
);
}
if (status == null) {
throw new IllegalArgumentException(
"Course status is required."
);
}
title =
title.strip();
}
}
This record represents a stable snapshot and protects its basic invariants।
Complete Example: Domain Entity + Record Snapshot
Course.java
package io.liveklass.course;
public final class Course {
private final CourseCode code;
private String title;
private CourseStatus status;
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.status =
CourseStatus.DRAFT;
}
public void changeTitle(
String newTitle
) {
if (
newTitle == null
|| newTitle.isBlank()
) {
throw new IllegalArgumentException(
"Course title is required."
);
}
title =
newTitle.strip();
}
public void publish() {
if (
status
!= CourseStatus.DRAFT
) {
throw new IllegalStateException(
"Only draft courses can be published."
);
}
status =
CourseStatus.PUBLISHED;
}
public CourseSummary summary() {
return new CourseSummary(
code,
title,
status
);
}
}
CourseSummary.java
package io.liveklass.course;
public record CourseSummary(
CourseCode code,
String title,
CourseStatus status
) {
public CourseSummary {
if (code == null) {
throw new IllegalArgumentException(
"Course code is required."
);
}
if (
title == null
|| title.isBlank()
) {
throw new IllegalArgumentException(
"Course title is required."
);
}
if (status == null) {
throw new IllegalArgumentException(
"Course status is required."
);
}
title =
title.strip();
}
}
Why This Split Works
Course is:
Entity
Mutable through controlled behavior
Owns lifecycle
CourseSummary is:
Immutable snapshot
Value-based equality
Easy to pass around
These are different responsibilities।
Common Mistakes
Treating Records as Automatically Deeply Immutable
Mutable components can still mutate।
Storing Mutable Collections Without Copying
External mutations can leak into the record।
Assuming Final References Mean Immutable Objects
They do not।
Using a Record for a Complex Mutable Entity
Can fight against the type's intended semantics।
Putting Business Services Inside a Record
Records are best when their primary identity is data/value representation।
Forgetting Constructor Validation
Records still allow invalid values unless you reject them।
Assuming Generated Equality Is Always Correct
It uses every component.
If those components do not define your desired equality, reconsider the record design।
Using Arrays Without Considering Their Equality and Mutability
Arrays are mutable and their default equality is reference-based।
Using Records Just to Reduce Lines
Boilerplate reduction is useful, but semantics come first।
Practice Exercises
Exercise 1: Convert to Record
Convert:
public final class LessonId {
private final long value;
// constructor
// getter
// equals
// hashCode
// toString
}
into a record।
Validate:
value > 0
Exercise 2: Course Summary
Create:
CourseSummary
with:
CourseCode code
String title
CourseStatus status
Validate all components।
Normalize title with:
strip()
Exercise 3: Immutable Course Plan
Create:
CoursePlan(
CourseCode courseCode,
List<LessonSummary> lessons
)
using a record।
Defensively copy:
lessons
Exercise 4: Find the Mutation Leak
public record Tags(
List<String> values
) {
}
Explain why this is not necessarily immutable and fix it।
Exercise 5: Entity or Record?
Choose the better starting design:
A
Course with publish(), archive(), addLesson()
B
CourseSearchResult with code, title, price
C
Money with amount and currency
D
Enrollment with lifecycle and cancellation rules
Suggested Answers
A → Normal class
B → Record
C → Record is a strong option
D → Normal class
Predict the Result
Question 1
public record Point(
int x,
int y
) {
}
Point first =
new Point(
10,
20
);
Point second =
new Point(
10,
20
);
System.out.println(
first.equals(
second
)
);
Answer
true
Generated record equality compares the components।
Question 2
Point point =
new Point(
10,
20
);
System.out.println(
point.x()
);
Answer
10
Question 3
Does a record automatically generate:
getX()
for component:
int x
?
Answer
No।
The generated accessor is:
x()
Question 4
Can a record extend your own:
BaseEntity
class?
Answer
No।
Question 5
Can a record implement an interface?
Answer
Yes।
True or False
- Records automatically generate value-based
equals(). - Record components are mutable fields.
- Records automatically deep-copy collection components.
- Records can contain methods.
- Records can implement interfaces.
- Records can extend arbitrary classes.
- Compact constructors can validate input.
- Record equality includes all components.
- Records are always better than normal classes.
- Records are good candidates for immutable snapshots and DTOs.
Answers
1. True
2. False
3. False
4. True
5. True
6. False
7. True
8. True
9. False
10. True
Knowledge Check
Question 1
What is a Java record?
Question 2
What is a record component?
Question 3
Which methods does Java generate for records?
Question 4
How are record accessor methods named?
Question 5
What is a compact constructor?
Question 6
Can record values be validated?
Question 7
Can constructor parameters be normalized in a compact constructor?
Question 8
Are records deeply immutable automatically?
Question 9
Why should collection components often use List.copyOf()?
Question 10
Why are arrays awkward record components?
Question 11
Why are records good value objects?
Question 12
Why might a lifecycle-heavy entity be better as a normal class?
Question 13
Can records implement interfaces?
Question 14
What fields participate in generated record equality?
Question 15
What is the most important rule when choosing between a record and normal class?
Knowledge Check Answers
Answer 1
A special Java type designed to concisely represent data-oriented values with generated state access, equality, hashing, and string representation।
Answer 2
A value declared in the record header that forms part of the record's state।
Answer 3
The canonical constructor, component accessors, equals(), hashCode(), and toString()।
Answer 4
Using the exact component names, such as:
code()
title()
Answer 5
A shortened canonical constructor syntax used mainly for validation and normalization।
Answer 6
Yes।
Answer 7
Yes. Reassign the constructor parameter before the automatic component assignment happens।
Answer 8
No. Mutable objects referenced by record components may still mutate।
Answer 9
To prevent external mutation of the stored collection structure।
Answer 10
Arrays are mutable and their normal equality is reference-based rather than content-based।
Answer 11
They naturally provide stable component-based equality, final state, and concise representation।
Answer 12
Entities often own identity, controlled mutation, invariants, and state transitions that are better expressed through a normal class।
Answer 13
Yes।
Answer 14
All record components।
Answer 15
Choose based on semantics: use a record when the type fundamentally represents a value or data snapshot; use a normal class when it primarily owns behavior, lifecycle, or controlled mutable state।
Lesson Summary
এই lesson-এ আমরা শিখেছি:
recorddata-oriented Java types concisely represent করে- Record header components complete state representation define করে
- Java automatically constructor, accessors,
equals(),hashCode(), andtoString()generate করে - Record accessors use
componentName(), not JavaBean-style getters - Records value-based equality provide করে
- Compact constructors validation এবং normalization support করে
- Records methods এবং static members contain করতে পারে
- Records interfaces implement করতে পারে
- Records arbitrary classes extend করতে পারে না
- Record components final হলেও referenced objects mutable হতে পারে
- Collections defensive copy করা প্রয়োজন হতে পারে
List.copyOf()structural mutation leak prevent করতে সাহায্য করে- Records automatically deep immutability provide করে না
- Arrays record components হিসেবে extra care require করে
- Generated equality uses every record component
- Records excellent value objects, DTOs, query results, validation errors, and snapshots
- Lifecycle-heavy mutable entities often normal class হিসেবে clearer
- A normal entity can expose an immutable record snapshot
- Records boilerplate reduce করে, design responsibility নয়
- Modern Java type নির্বাচন করার principle:
Value or snapshot → record is a strong candidate
Lifecycle and controlled mutation → normal class is often better
Next Lesson
পরবর্তী lesson:
Clean Methods and Class Design
আমরা শিখব:
- Single responsibility
- Cohesion
- Intention-revealing names
- Small focused methods
- Parameter design
- Avoiding boolean blindness
- Guard clauses
- Command-query separation
- Avoiding god classes
- Moving behavior to the right object
- Comments vs expressive code
- Refactoring long procedural methods into maintainable Java