Encapsulation
Java Master Course — Chapter 15 of 50
Encapsulation is one of the most important ideas in Object-Oriented Programming.
The simple idea is:
Keep an object's internal data protected and provide controlled ways to access or change it.
In Java, encapsulation is commonly implemented using:
private fields public/protected methods getters setters validation controlled operations defensive copying immutable design
1. What You Will Learn#
By the end of this chapter, you should understand:
- Encapsulation
- Data hiding
- Why encapsulation is needed
- private
- public
- protected preview
- Getters
- Setters
- Validation
- Controlled modification
- Read-only access
- Write-only concepts
- Business rules inside classes
- Encapsulating collections
- Defensive copying
- Returning unmodifiable views
- Mutable vs immutable objects
- final fields
- Constructor validation
- Setter validation
- Real-world class design
- BankAccount
- Student
- Employee
- Product
- Temperature
- Common mistakes
- Practical programs
- Exercises
- Output questions
- Interview questions
2. What Is Encapsulation?#
Encapsulation means putting:
data
+
methods that operate on that data
inside a class while controlling how outside code interacts with the object's internal state.
Example:
class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public double getBalance() {
return balance;
}
}
Here:
balance
↓
private
deposit()
getBalance()
↓
controlled access
3. Simple Definition#
A simple exam-friendly definition:
Encapsulation is the process of bundling data and the methods that operate on that data inside a class and restricting direct access to the internal state.
4. Encapsulation in Simple Language#
Imagine an ATM.
You do not directly access the bank's database and change:
balance = 1000000
Instead, you use operations such as:
deposit()
withdraw()
checkBalance()
The system controls what is allowed.
That is the basic idea behind encapsulation.
5. Real-World Example#
Consider a bank account.
Without encapsulation:
account.balance = -500000;
This could be invalid.
With encapsulation:
account.withdraw(500);
The class can check:
Is amount positive?
Is enough balance available?
Should the transaction be allowed?
The object controls its own state.
6. Why Do We Need Encapsulation?#
Without encapsulation, any accessible code may change an object's state directly.
Example:
student.marks = -100;
Possible problems:
invalid data
broken business rules
hard-to-find bugs
unexpected state changes
tight coupling
difficult maintenance
poor security boundaries
Encapsulation reduces these problems.
7. Encapsulation Protects Object State#
Think:
Outside code
|
| controlled methods
v
+----------------------+
| Object |
| |
| private data |
| private logic |
| business rules |
+----------------------+
Outside code does not need to know every internal detail.
8. Encapsulation Is Not Just Getters and Setters#
This is important.
Many beginners think:
encapsulation = private + getter + setter
That is incomplete.
Encapsulation is about:
controlled access
+
protecting invariants
+
hiding implementation details
+
keeping responsibilities inside the class
Sometimes a class should have a getter but no setter.
Sometimes it should have neither.
Sometimes the best API is a domain operation such as:
withdraw()
instead of:
setBalance()
9. What Is Data Hiding?#
Data hiding means preventing direct access to internal implementation details.
Example:
class BankAccount {
private double balance;
}
Outside code cannot directly do:
account.balance = 5000;
if balance is private.
10. private Access Modifier#
private is one of Java's access modifiers.
A private member is directly accessible only from within its declaring top-level class.
Example:
class Student {
private int marks;
}
This is invalid from outside:
Student s = new Student();
s.marks = 90;
because marks is private.
11. Why Use private?#
private helps a class control its internal state.
Instead of:
public double balance;
use:
private double balance;
Then expose only the operations that make sense.
12. Public Methods#
A public method can provide controlled access.
Example:
class Student {
private int marks;
public int getMarks() {
return marks;
}
}
Outside code can read the value:
Student s = new Student();
System.out.println(s.getMarks());
but cannot directly modify the private field.
13. Getter#
A getter is a method used to retrieve a value.
Example:
public String getName() {
return name;
}
Naming convention:
getFieldName()
14. Getter Example#
class Student {
private String name;
public String getName() {
return name;
}
}
Usage:
Student student = new Student();
System.out.println(student.getName());
15. Boolean Getter#
For boolean properties, Java convention commonly uses:
isActive()
rather than:
getActive()
Example:
class User {
private boolean active;
public boolean isActive() {
return active;
}
}
16. Setter#
A setter is a method used to change a field's value under the class's rules.
Example:
public void setName(String name) {
this.name = name;
}
Naming convention:
setFieldName(...)
17. Simple Getter + Setter#
class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Usage:
Student s = new Student();
s.setName("Aman");
System.out.println(s.getName());
Output:
Aman
18. Getter and Setter Do Not Automatically Make a Class Well Encapsulated#
Consider:
class BankAccount {
private double balance;
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
This technically hides direct field access, but:
account.setBalance(-100000);
may still violate the object's rules.
A better design may be:
deposit()
withdraw()
instead of unrestricted setBalance().
19. Encapsulation and Invariants#
An invariant is a condition that should remain true for a valid object.
For example:
Bank account balance should not become invalid.
Student marks should remain between 0 and 100.
Product price should not be negative.
Age should not be negative.
Encapsulation gives the class a place to enforce these rules.
20. Validation#
Validation checks whether data is acceptable.
Example:
public void setMarks(int marks) {
if (marks < 0 || marks > 100) {
throw new IllegalArgumentException(
"Marks must be between 0 and 100"
);
}
this.marks = marks;
}
Now invalid data is rejected.
21. Why Validate Inside the Class?#
Suppose ten different parts of a program create Student objects.
Without encapsulation:
Class A → checks marks
Class B → forgets check
Class C → uses wrong range
Class D → allows -10
With encapsulation:
all changes
↓
Student class
↓
one validation rule
This centralizes the business rule.
22. Encapsulation Example — Marks#
class Student {
private int marks;
public void setMarks(int marks) {
if (marks < 0 || marks > 100) {
throw new IllegalArgumentException(
"Marks must be 0 to 100"
);
}
this.marks = marks;
}
public int getMarks() {
return marks;
}
}
Usage:
Student s = new Student();
s.setMarks(85);
System.out.println(s.getMarks());
Output:
85
23. Invalid Value#
s.setMarks(150);
The method rejects it.
A possible exception is:
IllegalArgumentException
The exact message depends on the program.
24. Constructor Validation#
Validation should also happen when an object is created.
Example:
class Product {
private final double price;
Product(double price) {
if (price < 0) {
throw new IllegalArgumentException(
"Price cannot be negative"
);
}
this.price = price;
}
public double getPrice() {
return price;
}
}
25. Why Validate in Constructor?#
If an object must always satisfy a rule, it should not be allowed to start in an invalid state.
Bad:
create invalid object
↓
hope someone fixes it later
Better:
validate during construction
↓
valid object
26. Constructor + Setter Validation#
Example:
class Student {
private int marks;
Student(int marks) {
setMarks(marks);
}
public void setMarks(int marks) {
if (marks < 0 || marks > 100) {
throw new IllegalArgumentException(
"Invalid marks"
);
}
this.marks = marks;
}
public int getMarks() {
return marks;
}
}
The same validation rule is reused.
27. Encapsulation and final#
final can strengthen encapsulation.
Example:
class User {
private final int id;
User(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
There is no setter.
The ID cannot be reassigned through this field after construction.
28. Read-Only Property#
A field can be exposed through a getter without a setter.
Example:
class Employee {
private final int id;
Employee(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
Outside code can read:
employee.getId();
but cannot call:
employee.setId(...);
because no setter exists.
29. Read-Only Does Not Always Mean Immutable#
Suppose:
class User {
private final List<String> roles;
public List<String> getRoles() {
return roles;
}
}
There is no setter, but callers may still mutate the list:
user.getRoles().add("ADMIN");
So simply removing a setter is not enough for mutable objects.
30. Defensive Copying#
A defensive copy prevents outside code from directly modifying internal mutable state.
Example:
class User {
private final List<String> roles;
User(List<String> roles) {
this.roles = new ArrayList<>(roles);
}
public List<String> getRoles() {
return new ArrayList<>(roles);
}
}
The class owns its internal list.
31. Why Copy in the Constructor?#
Without copying:
List<String> external = new ArrayList<>();
User user = new User(external);
external.add("ADMIN");
If the class stores external directly, its internal state changes from outside.
With:
new ArrayList<>(roles)
the class gets its own list.
32. Defensive Copy on Getter#
Suppose:
return new ArrayList<>(roles);
The caller receives another list.
Then:
user.getRoles().add("ADMIN");
changes the returned copy, not the internal list.
33. Unmodifiable View#
Another approach is an unmodifiable view:
return Collections.unmodifiableList(roles);
This prevents callers from modifying through the returned view.
The underlying list is still mutable by the class itself.
34. Modern Java Example#
return List.copyOf(roles);
can return an unmodifiable copy.
Example:
public List<String> getRoles() {
return List.copyOf(roles);
}
This is often a clean choice when an immutable snapshot is desired.
35. Defensive Copying — Important Difference#
new ArrayList<>(list)
→ mutable copy
List.copyOf(list)
→ unmodifiable copy
Collections.unmodifiableList(list)
→ unmodifiable view backed by the original list
These have different semantics.
36. Encapsulation of Arrays#
Arrays are mutable too.
Bad:
class Student {
private int[] marks;
int[] getMarks() {
return marks;
}
}
Caller can do:
student.getMarks()[0] = -100;
The private field was modified indirectly.
37. Defensive Copy of Array#
Better:
class Student {
private int[] marks;
Student(int[] marks) {
this.marks = marks.clone();
}
int[] getMarks() {
return marks.clone();
}
}
Now external code cannot directly modify the internal array through the original reference or getter result.
38. Reference Types and Encapsulation#
This is very important.
For primitive:
private int age;
returning:
return age;
returns a value.
For a mutable object:
private List<String> names;
returning:
return names;
returns a reference to the same list.
That reference can expose internal state.
39. Encapsulation and References#
Remember from previous chapters:
object variable
↓
reference
↓
object
If you give outside code the same reference to a mutable internal object, outside code may be able to change your object's internal state.
40. Example of Broken Encapsulation#
class Team {
private List<String> players =
new ArrayList<>();
public List<String> getPlayers() {
return players;
}
}
Then:
Team team = new Team();
team.getPlayers().add("Aman");
The private list changed.
This is not necessarily always wrong, but it is not strong encapsulation if outside code was not supposed to mutate the list.
41. Better Encapsulation#
class Team {
private final List<String> players =
new ArrayList<>();
public List<String> getPlayers() {
return List.copyOf(players);
}
public void addPlayer(String player) {
if (player == null || player.isBlank()) {
throw new IllegalArgumentException(
"Player name is required"
);
}
players.add(player);
}
}
Now the class controls modifications.
42. Domain Methods Are Often Better Than Setters#
Instead of:
account.setBalance(
account.getBalance() - amount
);
use:
account.withdraw(amount);
Why?
Because withdraw() can enforce:
amount > 0
balance sufficient
account active
transaction allowed
The class owns the rule.
43. Encapsulation Is About Behavior Too#
Good class:
class BankAccount {
private double balance;
public void withdraw(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException();
}
if (amount > balance) {
throw new IllegalArgumentException(
"Insufficient balance"
);
}
balance -= amount;
}
}
The caller says:
withdraw 500
instead of manipulating the internal representation.
44. Tell the Object What to Do#
A useful OOP idea:
Bad style:
get data
modify data outside
set data back
Better:
ask object to perform operation
Example:
account.withdraw(500);
instead of:
account.setBalance(
account.getBalance() - 500
);
45. Encapsulation and Abstraction#
These concepts are related but not identical.
Encapsulation
→ controls access to internal state
Abstraction
→ exposes important behavior while hiding unnecessary implementation details
Encapsulation is often used to achieve abstraction.
46. Example#
class Car {
private Engine engine;
public void start() {
engine.start();
}
}
The caller does not need direct access to every engine detail.
The Car exposes:
start()
This demonstrates both encapsulation and abstraction ideas.
47. Encapsulation and Information Hiding#
Information hiding means implementation details should not unnecessarily become part of the public API.
Example:
private double balance;
The outside world does not need to know exactly how the account stores its balance.
The class can later change internal representation while preserving its public contract.
48. Why This Helps Maintenance#
Suppose you expose:
public double balance;
Many classes may directly depend on it.
Changing the representation becomes difficult.
With:
private double balance;
and:
getBalance()
withdraw()
deposit()
the internal implementation can change with less impact on callers.
49. Public API#
The methods accessible to outside code form part of the class's public API.
Example:
public void deposit(double amount)
public void withdraw(double amount)
public double getBalance()
These define how other code interacts with the object.
50. Private Implementation#
Implementation details can remain private:
private double balance;
private boolean active;
private void validateAmount(double amount)
Outside code does not directly depend on them.
51. Private Helper Method#
Encapsulation also applies to methods.
Example:
class BankAccount {
private double balance;
public void deposit(double amount) {
validateAmount(amount);
balance += amount;
}
private void validateAmount(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException();
}
}
}
The validation helper is an internal implementation detail.
52. Benefits of Private Helper Methods#
They provide:
code reuse
clear public API
centralized logic
less duplication
better readability
53. Encapsulation Does Not Mean Everything Must Be Private#
Not every member must be private.
The goal is appropriate access control.
Example:
public class Student {
private String name;
public String getName() {
return name;
}
public void study() {
}
}
Public behavior is expected.
Internal state is protected.
54. Access Modifiers Preview#
Java commonly provides:
private
default/package-private
protected
public
Their detailed package/inheritance behavior is covered in Chapter 24.
For encapsulation, private is especially important.
55. Package-Private Members#
If no access modifier is written:
class Student {
String name;
}
then name has package-private access.
It is accessible to classes in the same package, subject to normal package rules.
This is different from private.
56. protected Preview#
protected provides access within the same package and also to subclasses under Java's protected-access rules.
Detailed behavior will be covered with packages and access modifiers.
57. Encapsulation Example — Employee#
class Employee {
private String name;
private double salary;
public String getName() {
return name;
}
public void setName(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException(
"Name cannot be empty"
);
}
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
if (salary < 0) {
throw new IllegalArgumentException(
"Salary cannot be negative"
);
}
this.salary = salary;
}
}
58. Better Employee Design#
Sometimes a setter for salary is not the best business API.
Instead:
public void increaseSalary(double percentage) {
if (percentage < 0) {
throw new IllegalArgumentException();
}
salary += salary * percentage / 100;
}
This expresses a business operation.
59. Why Domain Operations Are Powerful#
Compare:
employee.setSalary(65000);
with:
employee.increaseSalary(10);
The second tells the object what operation should happen.
The class controls how the operation is implemented.
60. Encapsulation Example — Bank Account#
class BankAccount {
private final String accountNumber;
private double balance;
BankAccount(String accountNumber,
double initialBalance) {
if (accountNumber == null ||
accountNumber.isBlank()) {
throw new IllegalArgumentException(
"Account number is required"
);
}
if (initialBalance < 0) {
throw new IllegalArgumentException(
"Initial balance cannot be negative"
);
}
this.accountNumber = accountNumber;
this.balance = initialBalance;
}
public String getAccountNumber() {
return accountNumber;
}
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException(
"Deposit must be positive"
);
}
balance += amount;
}
public void withdraw(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException(
"Withdrawal must be positive"
);
}
if (amount > balance) {
throw new IllegalArgumentException(
"Insufficient balance"
);
}
balance -= amount;
}
}
61. Why This BankAccount Is Encapsulated#
The caller cannot directly do:
account.balance = -100000;
Instead:
account.deposit(1000);
account.withdraw(500);
The object controls valid state changes.
62. Practical BankAccount Program#
public class Main {
public static void main(String[] args) {
BankAccount account =
new BankAccount("ACC101", 1000);
account.deposit(500);
account.withdraw(300);
System.out.println(
account.getBalance()
);
}
}
Output:
1200.0
63. Encapsulation Example — Temperature#
class Temperature {
private double celsius;
public Temperature(double celsius) {
setCelsius(celsius);
}
public double getCelsius() {
return celsius;
}
public void setCelsius(double celsius) {
if (celsius < -273.15) {
throw new IllegalArgumentException(
"Below absolute zero"
);
}
this.celsius = celsius;
}
}
64. Why Validation Belongs Here#
The rule:
Celsius >= -273.15
belongs naturally to Temperature.
Every caller should not have to remember this rule.
65. Encapsulation Example — Product#
class Product {
private final String id;
private String name;
private double price;
Product(String id, String name, double price) {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException();
}
this.id = id;
setName(name);
setPrice(price);
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException();
}
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
if (price < 0) {
throw new IllegalArgumentException();
}
this.price = price;
}
}
66. Encapsulation Example — Student#
class Student {
private final int rollNumber;
private String name;
private int marks;
Student(int rollNumber,
String name,
int marks) {
this.rollNumber = rollNumber;
setName(name);
setMarks(marks);
}
public int getRollNumber() {
return rollNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException();
}
this.name = name;
}
public int getMarks() {
return marks;
}
public void setMarks(int marks) {
if (marks < 0 || marks > 100) {
throw new IllegalArgumentException();
}
this.marks = marks;
}
}
67. Read-Only Student Roll Number#
Notice:
private final int rollNumber;
and:
public int getRollNumber()
There is no:
setRollNumber()
because the roll number is intended to be fixed after construction.
68. Encapsulation of Calculated Values#
A getter does not have to return a stored field.
Example:
class Rectangle {
private final double width;
private final double height;
Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double getArea() {
return width * height;
}
}
There is no need for:
private double area;
because area can be calculated.
69. Encapsulation Hides Representation#
Suppose initially:
private double area;
Later you change to:
public double getArea() {
return width * height;
}
Callers can still use:
rectangle.getArea();
The internal representation changed.
This is one major benefit of encapsulation.
70. Encapsulation and Internal Algorithms#
Suppose:
public double getTotal() {
return price * quantity;
}
The caller only cares about:
getTotal()
It does not need to know how the calculation is performed.
71. Encapsulation and Method Contracts#
A public method should have a clear contract.
Example:
deposit(500)
may guarantee:
amount must be positive
balance increases by amount
The internal implementation may change without changing the public contract.
72. Encapsulation and Coupling#
Without encapsulation:
Class A
↓
directly manipulates
↓
Class B's internal fields
This creates stronger coupling.
With encapsulation:
Class A
↓
public API
↓
Class B
Class B controls its internal representation.
73. Encapsulation and Cohesion#
A well-encapsulated class often has good cohesion.
Its data and behavior are related.
Example:
class BankAccount {
private double balance;
void deposit() {
}
void withdraw() {
}
}
The balance and account operations belong together.
74. Common Mistake — Public Fields#
Avoid:
class Student {
public String name;
public int marks;
}
when the object needs validation or controlled state.
Better:
class Student {
private String name;
private int marks;
public void setMarks(int marks) {
// validation
}
}
75. Common Mistake — Unrestricted Setters#
This:
public void setBalance(double balance) {
this.balance = balance;
}
may be dangerous if arbitrary balance changes are not valid operations.
Prefer domain-specific methods when appropriate.
76. Common Mistake — Returning Mutable Internal State#
Avoid:
public List<String> getItems() {
return items;
}
if callers should not be able to modify the internal list.
Use a defensive copy or unmodifiable result as appropriate.
77. Common Mistake — Trusting Constructor Inputs#
Bad:
Student(int marks) {
this.marks = marks;
}
if marks must be between 0 and 100.
Better:
Student(int marks) {
setMarks(marks);
}
or validate directly.
78. Common Mistake — Validation Only at Input Layer#
Suppose the UI checks:
marks 0–100
but another API creates Student objects directly.
The class should still protect its own invariant when necessary.
Do not rely only on one external caller.
79. Common Mistake — Getter for Everything#
You do not need a getter for every field.
Ask:
Does outside code really need this information?
If not, keep it private.
80. Common Mistake — Setter for Everything#
You do not need a setter for every field.
Ask:
Should this value be freely changed?
If not, omit the setter or expose a domain operation.
81. Common Mistake — Making Fields final Without Understanding References#
Example:
private final List<String> items =
new ArrayList<>();
final prevents:
items = anotherList;
but does not automatically prevent:
items.add("Java");
The list remains mutable.
82. Immutable Object#
An immutable object is an object whose observable state cannot be changed after construction.
A common design uses:
private final fields
no mutating setters
constructor initialization
defensive copies for mutable components
83. Simple Immutable Class#
final class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
After construction:
x cannot change
y cannot change
84. Why Make a Class final?#
A final class cannot be subclassed.
For some immutable designs, making the class final helps prevent subclasses from introducing mutable state or behavior that breaks the intended immutability model.
It is one useful tool, though immutability requires more than simply writing final class.
85. Immutable Class with String#
final class User {
private final String name;
User(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
String is immutable, so returning the String does not expose mutable internal state.
86. Immutable Class with Mutable Field#
Consider:
final class Team {
private final List<String> players;
Team(List<String> players) {
this.players = List.copyOf(players);
}
public List<String> getPlayers() {
return players;
}
}
List.copyOf() provides an unmodifiable copy.
This is much safer.
87. Encapsulation vs Immutability#
They are not the same.
Encapsulation
→ controls access
Immutability
→ state cannot change after construction
An object can be encapsulated but mutable.
Example:
account.deposit(500);
The balance changes, but the field remains private.
88. Encapsulation vs Data Hiding#
Data hiding is one part of encapsulation.
Think:
Encapsulation
├── data + behavior together
├── controlled access
├── validation
├── implementation hiding
└── protected invariants
89. Encapsulation vs Abstraction#
Simple distinction:
Encapsulation:
How do we protect/control the internal state?
Abstraction:
What important interface/behavior do we expose while hiding unnecessary details?
90. Example#
class Car {
private Engine engine;
public void start() {
engine.start();
}
}
Encapsulation:
engine is private
Abstraction:
caller uses start()
without needing engine internals
91. Encapsulation and Composition#
Encapsulation works well with composition.
Example:
class Car {
private Engine engine;
public void start() {
engine.start();
}
}
The Car controls access to its internal Engine reference.
92. Encapsulation of Business Rules#
Example:
class ShoppingCart {
private final List<Item> items =
new ArrayList<>();
public void addItem(Item item) {
if (item == null) {
throw new IllegalArgumentException();
}
items.add(item);
}
public double getTotal() {
return items.stream()
.mapToDouble(Item::getPrice)
.sum();
}
}
The cart owns the rules for adding items and calculating its total.
93. Do Not Expose Internal Collections Unnecessarily#
Instead of:
cart.getItems().clear();
you may expose:
cart.clear();
if clearing is a valid domain operation.
This lets the class decide what clearing means.
94. Better Collection API#
class ShoppingCart {
private final List<Item> items =
new ArrayList<>();
public void addItem(Item item) {
items.add(item);
}
public void removeItem(Item item) {
items.remove(item);
}
public List<Item> getItems() {
return List.copyOf(items);
}
}
Outside code can inspect the items without directly modifying the internal list.
95. Encapsulation and Null#
A setter can define whether null is allowed.
Example:
public void setName(String name) {
if (name == null) {
throw new IllegalArgumentException(
"Name cannot be null"
);
}
this.name = name;
}
The class establishes its own rule.
96. Encapsulation and Validation Strategy#
Validation may include:
null checks
range checks
format checks
state checks
cross-field checks
business rules
Example:
startDate <= endDate
This is a class-level invariant.
97. Cross-Field Validation#
Example:
class DateRange {
private final int start;
private final int end;
DateRange(int start, int end) {
if (start > end) {
throw new IllegalArgumentException(
"Start cannot be after end"
);
}
this.start = start;
this.end = end;
}
}
Encapsulation keeps the invariant inside the object.
98. Controlled State Transitions#
Some objects should allow only certain transitions.
Example:
Order:
NEW
↓
PAID
↓
SHIPPED
↓
DELIVERED
A bad design might expose:
setStatus(...)
A better design might expose:
pay()
ship()
deliver()
Each method can enforce valid transitions.
99. State Machine Example#
class Order {
private String status = "NEW";
public void pay() {
if (!status.equals("NEW")) {
throw new IllegalStateException(
"Order cannot be paid"
);
}
status = "PAID";
}
public String getStatus() {
return status;
}
}
Outside code cannot arbitrarily assign:
DELIVERED
without following the object's rules.
100. Encapsulation Protects Invariants, Not Secrets#
private is about access control and program design.
It is not a security mechanism for storing secrets against determined runtime inspection.
Do not think:
private = encrypted
They are completely different concepts.
101. Practical Program — Secure-Looking Account#
class BankAccount {
private final String accountNumber;
private double balance;
BankAccount(String accountNumber,
double balance) {
if (accountNumber == null ||
accountNumber.isBlank()) {
throw new IllegalArgumentException();
}
if (balance < 0) {
throw new IllegalArgumentException();
}
this.accountNumber = accountNumber;
this.balance = balance;
}
public String getAccountNumber() {
return accountNumber;
}
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException();
}
balance += amount;
}
public boolean withdraw(double amount) {
if (amount <= 0 || amount > balance) {
return false;
}
balance -= amount;
return true;
}
}
102. Practical Program — Student Result#
class Student {
private final String name;
private int marks;
Student(String name, int marks) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException();
}
this.name = name;
setMarks(marks);
}
public String getName() {
return name;
}
public int getMarks() {
return marks;
}
public void setMarks(int marks) {
if (marks < 0 || marks > 100) {
throw new IllegalArgumentException();
}
this.marks = marks;
}
public boolean hasPassed() {
return marks >= 40;
}
}
103. Practical Program — Product Price#
class Product {
private final String id;
private double price;
Product(String id, double price) {
this.id = id;
setPrice(price);
}
public String getId() {
return id;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
if (price < 0) {
throw new IllegalArgumentException(
"Price cannot be negative"
);
}
this.price = price;
}
public void applyDiscount(double percent) {
if (percent < 0 || percent > 100) {
throw new IllegalArgumentException(
"Invalid discount"
);
}
price -= price * percent / 100;
}
}
104. Practical Program — Employee Salary#
class Employee {
private final int id;
private String name;
private double salary;
Employee(int id, String name, double salary) {
this.id = id;
setName(name);
setSalary(salary);
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException();
}
this.name = name;
}
public double getSalary() {
return salary;
}
public void increaseSalary(double percent) {
if (percent < 0) {
throw new IllegalArgumentException();
}
salary += salary * percent / 100;
}
private void setSalary(double salary) {
if (salary < 0) {
throw new IllegalArgumentException();
}
this.salary = salary;
}
}
Notice that there is no public salary setter.
105. Why Private setSalary()?#
Suppose salary should only change through:
increaseSalary()
Then a public setter would allow:
employee.setSalary(-100);
or arbitrary changes.
A private helper can still be used internally.
106. Practical Program — Encapsulated Counter#
class Counter {
private int value;
public int getValue() {
return value;
}
public void increment() {
value++;
}
public void decrement() {
if (value > 0) {
value--;
}
}
}
The caller cannot directly set:
counter.value = -100;
107. Practical Program — Immutable Point#
final class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Point move(int dx, int dy) {
return new Point(
x + dx,
y + dy
);
}
}
Instead of changing the existing Point, move() returns a new Point.
108. Immutable Point Usage#
Point p1 = new Point(10, 20);
Point p2 = p1.move(5, 5);
System.out.println(p1.getX());
System.out.println(p1.getY());
System.out.println(p2.getX());
System.out.println(p2.getY());
Output:
10
20
15
25
109. Practical Program — Encapsulated List#
import java.util.ArrayList;
import java.util.List;
class Playlist {
private final List<String> songs =
new ArrayList<>();
public void addSong(String song) {
if (song == null || song.isBlank()) {
throw new IllegalArgumentException();
}
songs.add(song);
}
public void removeSong(String song) {
songs.remove(song);
}
public List<String> getSongs() {
return List.copyOf(songs);
}
}
110. Playlist Usage#
Playlist playlist = new Playlist();
playlist.addSong("Java");
playlist.addSong("Python");
System.out.println(playlist.getSongs());
Possible output:
[Java, Python]
The returned list cannot be modified through normal List mutation methods.
111. Practical Program — Read-Only ID#
class User {
private final int id;
private String name;
User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
The ID is read-only through the public API.
112. Practical Program — Encapsulated Temperature#
class Temperature {
private double celsius;
Temperature(double celsius) {
setCelsius(celsius);
}
public double getCelsius() {
return celsius;
}
public void setCelsius(double celsius) {
if (celsius < -273.15) {
throw new IllegalArgumentException(
"Invalid temperature"
);
}
this.celsius = celsius;
}
public double getFahrenheit() {
return celsius * 9 / 5 + 32;
}
}
113. Practical Program — Encapsulated Rectangle#
class Rectangle {
private final double width;
private final double height;
Rectangle(double width, double height) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException(
"Dimensions must be positive"
);
}
this.width = width;
this.height = height;
}
public double getWidth() {
return width;
}
public double getHeight() {
return height;
}
public double getArea() {
return width * height;
}
public double getPerimeter() {
return 2 * (width + height);
}
}
114. Practical Program — Encapsulated Order#
class Order {
private String status = "NEW";
public String getStatus() {
return status;
}
public void pay() {
if (!status.equals("NEW")) {
throw new IllegalStateException(
"Invalid state"
);
}
status = "PAID";
}
public void ship() {
if (!status.equals("PAID")) {
throw new IllegalStateException(
"Order must be paid first"
);
}
status = "SHIPPED";
}
}
115. Order Usage#
Order order = new Order();
System.out.println(order.getStatus());
order.pay();
System.out.println(order.getStatus());
order.ship();
System.out.println(order.getStatus());
Output:
NEW
PAID
SHIPPED
116. Encapsulation with Private Helper#
class PasswordPolicy {
public boolean isValid(String password) {
return hasMinimumLength(password)
&& hasDigit(password);
}
private boolean hasMinimumLength(String password) {
return password != null &&
password.length() >= 8;
}
private boolean hasDigit(String password) {
if (password == null) {
return false;
}
return password.chars()
.anyMatch(Character::isDigit);
}
}
The caller only needs:
isValid()
The helper methods remain implementation details.
117. Encapsulation and API Evolution#
Suppose you start with:
public double getPrice() {
return price;
}
Later you add:
discounts
taxes
currency conversion
rounding
You can change the internal implementation while preserving:
getPrice()
This is easier than exposing a public field.
118. Why Public Fields Increase Coupling#
Suppose:
public int marks;
Many classes may write:
student.marks = 80;
Now those classes depend on:
field name
field type
field representation
allowed values
Changing the design becomes harder.
119. Encapsulation Reduces Representation Dependency#
With:
private int marks;
public int getMarks()
callers depend mainly on the method contract.
Internally you might later change how marks are stored.
120. Encapsulation and Testing#
A well-designed class is easier to test because its rules are centralized.
Example:
student.setMarks(101);
should always be rejected.
You can test the Student class directly rather than relying on every caller to perform the same validation.
121. Encapsulation and Debugging#
When all state changes happen through controlled methods:
setMarks()
deposit()
withdraw()
increaseSalary()
it becomes easier to find where a state change occurred.
With public fields:
field can be changed from many places
which makes debugging harder.
122. Encapsulation and Security#
Encapsulation improves software design and prevents accidental misuse.
But remember:
private ≠ encryption
private ≠ complete security
Java access modifiers control source-level access according to Java's access rules.
They are not a substitute for authentication, authorization, encryption, or secure storage.
123. Encapsulation Checklist#
When designing a class, ask:
1. Which data should be private?
2. Which values have valid ranges?
3. Which values must never change?
4. Which operations are actually allowed?
5. Do I need getters?
6. Do I need setters?
7. Should a setter validate?
8. Should a domain operation replace a setter?
9. Am I exposing a mutable object?
10. Do I need a defensive copy?
11. What invariants must always hold?
12. Can internal representation change later?
124. Good Encapsulation Pattern#
A common pattern is:
class ClassName {
private fields;
constructor validates;
public getters when needed;
public domain methods;
private helper methods;
no unnecessary setters;
}
125. Weak Encapsulation Pattern#
Avoid blindly writing:
class Student {
public String name;
public int age;
public int marks;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getMarks() {
return marks;
}
public void setMarks(int marks) {
this.marks = marks;
}
}
This exposes the state publicly while adding unnecessary boilerplate.
126. Better Student Design#
class Student {
private final int rollNumber;
private String name;
private int marks;
Student(int rollNumber,
String name,
int marks) {
this.rollNumber = rollNumber;
setName(name);
setMarks(marks);
}
public int getRollNumber() {
return rollNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException();
}
this.name = name;
}
public int getMarks() {
return marks;
}
public void setMarks(int marks) {
if (marks < 0 || marks > 100) {
throw new IllegalArgumentException();
}
this.marks = marks;
}
public boolean hasPassed() {
return marks >= 40;
}
}
127. Encapsulation Is About Responsibility#
A powerful OOP principle:
The class that owns the state should usually own the rules for changing that state.
Example:
BankAccount
↓
balance
↓
deposit / withdraw
rather than:
SomeOtherClass
↓
directly changes balance
128. Encapsulation and Object Responsibility#
A good object knows:
its state
+
how to keep its state valid
+
what operations make sense for that state
This makes the object more self-contained.
129. Encapsulation and Coupling Example#
Poor design:
account.setBalance(
account.getBalance() - amount
);
The caller knows too much.
Better:
account.withdraw(amount);
The caller knows the operation, not the internal state transition.
130. Encapsulation and Abstraction Example#
Caller:
account.withdraw(500);
does not need to know whether the account internally uses:
double
BigDecimal
database-backed balance
ledger entries
transaction objects
The public API hides implementation details.
131. Important Note About Money#
For educational examples, double is convenient.
For real financial applications, double is generally not appropriate for exact monetary calculations because binary floating-point cannot represent many decimal fractions exactly.
A production financial model may use:
BigDecimal
or another appropriate monetary representation.
This chapter focuses on encapsulation, so the simple examples use double.
132. Encapsulation and BigDecimal Preview#
A more realistic money field might be:
private BigDecimal balance;
with:
public void deposit(BigDecimal amount)
The encapsulation principle stays the same.
The representation changes.
133. Practical Mini Project — Bank Account#
Build:
BankAccount
Requirements:
private accountNumber
private ownerName
private balance
constructor validation
getAccountNumber()
getOwnerName()
getBalance()
deposit()
withdraw()
private validation methods
Rules:
account number cannot be empty
owner name cannot be empty
balance cannot start negative
deposit must be positive
withdrawal must be positive
withdrawal cannot exceed balance
134. Mini Project — Student Management#
Create:
Student
Fields:
rollNumber
name
marks
Requirements:
rollNumber → read-only
name → getter/setter
marks → getter/setter with validation
hasPassed()
getGrade()
Suggested grading:
90–100 → A
80–89 → B
70–79 → C
60–69 → D
40–59 → E
0–39 → F
135. Mini Project — Product#
Create:
Product
Fields:
id
name
price
Rules:
id cannot change
name cannot be empty
price cannot be negative
Methods:
getId()
getName()
setName()
getPrice()
setPrice()
applyDiscount()
136. Mini Project — Order#
Create:
Order
State:
NEW
PAID
SHIPPED
DELIVERED
CANCELLED
Do not provide:
setStatus()
Instead provide:
pay()
ship()
deliver()
cancel()
Validate valid state transitions.
This is an excellent encapsulation exercise.
137. Mini Project — Playlist#
Create:
Playlist
Internal:
private final List<String> songs;
Methods:
addSong()
removeSong()
getSongs()
getSongCount()
clear()
Do not expose the mutable internal list directly.
138. Practice Questions#
Question 1#
What is encapsulation?
Question 2#
Why are fields commonly declared private?
Question 3#
What is the purpose of a getter?
Question 4#
What is the purpose of a setter?
Question 5#
Why might a class have a getter but no setter?
Question 6#
Why can a setter be dangerous if it has no validation?
Question 7#
What is data hiding?
Question 8#
Is encapsulation the same as abstraction?
Question 9#
Why should a BankAccount not normally expose a public balance field?
Question 10#
Why can returning a private List still break encapsulation?
139. More Practice Questions#
Question 11#
What is a defensive copy?
Question 12#
What is the difference between:
return list;
and:
return List.copyOf(list);
Question 13#
Why can final List<String> still be mutable?
Question 14#
Why should constructor inputs sometimes be validated?
Question 15#
What is an invariant?
Question 16#
Why are domain methods sometimes better than setters?
Question 17#
Why is withdraw() often better than setBalance()?
Question 18#
What is a read-only property?
Question 19#
What is an immutable object?
Question 20#
Can an immutable class contain a mutable object?
Explain what extra work is required.
140. Output Questions#
Output 1#
class Student {
private int marks = 80;
public int getMarks() {
return marks;
}
}
Student s = new Student();
System.out.println(s.getMarks());
Output:
80
Output 2#
class Student {
private int marks;
public void setMarks(int marks) {
if (marks >= 0 && marks <= 100) {
this.marks = marks;
}
}
public int getMarks() {
return marks;
}
}
If:
Student s = new Student();
s.setMarks(150);
System.out.println(s.getMarks());
Output:
0
because the invalid value is rejected and the field retains its default value.
Output 3#
class Student {
private int marks;
public void setMarks(int marks) {
if (marks < 0 || marks > 100) {
throw new IllegalArgumentException();
}
this.marks = marks;
}
}
If:
Student s = new Student();
s.setMarks(150);
Result:
IllegalArgumentException
Output 4#
class User {
private final int id;
User(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
There is no public setter.
Therefore:
user.setId(20);
does not compile because the method does not exist.
Output 5#
class Test {
private int x = 10;
public int getX() {
return x;
}
}
This is valid:
Test t = new Test();
System.out.println(t.getX());
Output:
10
141. Interview Questions — Basic#
Q1. What is encapsulation in Java?#
Encapsulation is the bundling of state and behavior inside a class while controlling access to the internal state.
Q2. How is encapsulation commonly achieved in Java?#
Commonly through:
private fields
public/protected methods
controlled operations
validation
appropriate access modifiers
Q3. What is data hiding?#
Data hiding means restricting direct access to internal implementation details, often using private.
Q4. Why should fields usually be private?#
Private fields allow the class to control how state is read and modified and reduce direct coupling to its representation.
Q5. What is a getter?#
A getter is a method that provides read access to a value.
Example:
public int getAge() {
return age;
}
Q6. What is a setter?#
A setter is a method that changes a value under whatever rules the class defines.
Example:
public void setAge(int age) {
this.age = age;
}
Q7. Does every private field need a getter and setter?#
No.
Only expose operations that make sense for the class's public API.
Q8. Why might a field have a getter but no setter?#
The value may be intended to be read-only after construction.
Example:
private final int id;
Q9. Why can a setter be better than direct field access?#
A setter can:
validate input
normalize input
enforce business rules
trigger related behavior
Q10. What is an invariant?#
An invariant is a condition that should remain true for a valid object's state.
142. Interview Questions — Intermediate#
Q11. Is encapsulation just private variables plus getters and setters?#
No.
That is only one common implementation technique.
Good encapsulation also involves:
controlled behavior
invariants
implementation hiding
appropriate public APIs
Q12. What is defensive copying?#
Creating a separate copy of mutable data so external code cannot directly modify an object's internal mutable state.
Q13. Why is returning a mutable collection dangerous?#
Because the caller may receive a reference to the internal collection and mutate the object's internal state.
Q14. How can you safely expose a collection?#
Depending on requirements:
return new ArrayList<>(items);
or:
return List.copyOf(items);
or:
return Collections.unmodifiableList(items);
Each has different semantics.
Q15. What is the difference between defensive copy and unmodifiable view?#
A defensive copy creates a separate collection.
An unmodifiable view prevents modification through that view but can reflect changes made to the original collection by the owning class.
Q16. Does private make an object immutable?#
No.
private controls direct access.
An object can still change through public methods.
Q17. Does final make an object immutable?#
No.
final prevents reassignment of a variable/reference, but the referenced object may still be mutable.
Q18. Can a class be encapsulated but mutable?#
Yes.
Example:
class BankAccount {
private double balance;
public void deposit(double amount) {
balance += amount;
}
}
The state changes, but direct access is controlled.
Q19. Can an immutable class contain a mutable object?#
Yes, but it must prevent external mutation of that mutable object, often through defensive copying or immutable representations.
Q20. Why are domain methods often better than generic setters?#
Domain methods express valid operations and allow the class to enforce business rules.
Example:
withdraw(500)
is often more meaningful than:
setBalance(4500)
143. Interview Questions — Advanced#
Q21. How does encapsulation reduce coupling?#
It prevents outside classes from depending directly on internal representation.
Q22. How does encapsulation support maintainability?#
Internal implementation can change while the public API remains stable.
Q23. What is representation independence?#
The ability to change how an object stores its data without requiring callers to change, as long as the public behavior remains compatible.
Q24. Why is a public mutable field often poor design?#
Any caller can modify it without validation or controlled behavior, creating stronger coupling and potentially invalid state.
Q25. What is a class invariant?#
A condition that the class guarantees for valid instances.
Q26. Should validation happen in constructors?#
If the object must satisfy a rule from the moment it exists, validating constructor inputs is appropriate.
Q27. Should validation happen only in setters?#
Not necessarily.
Constructors and other state-changing methods should also maintain the object's invariants.
Q28. Can encapsulation improve security?#
It can reduce accidental misuse and establish controlled boundaries, but Java access modifiers are not a substitute for real security mechanisms.
Q29. What is information hiding?#
Keeping implementation details private so clients depend on behavior rather than internal representation.
Q30. What is the relationship between encapsulation and abstraction?#
Encapsulation controls and protects implementation/state; abstraction focuses on exposing the essential interface while hiding unnecessary complexity. They often work together.
144. Common Interview Trap#
Question:
If a field is private and has a public setter, is the data completely protected?
Answer:
No.
Example:
private int balance;
public void setBalance(int balance) {
this.balance = balance;
}
The field cannot be accessed directly, but the public setter may allow arbitrary changes.
Good encapsulation requires a meaningful API and appropriate validation.
145. Common Interview Trap#
Question:
Is
privateenough to make a class immutable?
Answer:
No.
Example:
class Counter {
private int value;
public void increment() {
value++;
}
}
The field is private but the object is mutable.
146. Common Interview Trap#
Question:
Is
finalenough to make a List immutable?
Answer:
No.
private final List<String> list =
new ArrayList<>();
The reference cannot be reassigned, but the list can still be changed.
147. Common Interview Trap#
Question:
Is returning a private object field safe?
Answer:
It depends.
For an immutable object such as String, returning the reference is generally safe from mutation.
For a mutable object such as ArrayList, returning the internal reference may expose mutable state.
148. Encapsulation Design Rules#
Remember these rules:
1. Keep internal state private when possible.
2. Expose behavior rather than raw representation.
3. Validate state-changing inputs.
4. Do not create setters automatically for every field.
5. Use final when a field should not be reassigned.
6. Protect mutable internal objects from external mutation.
7. Prefer domain-specific operations.
8. Keep implementation helpers private.
9. Maintain class invariants.
10. Design the public API around what callers actually need.
149. One Complete Example#
import java.util.ArrayList;
import java.util.List;
final class BankAccount {
private final String accountNumber;
private final String ownerName;
private double balance;
private final List<String> transactions =
new ArrayList<>();
BankAccount(
String accountNumber,
String ownerName,
double initialBalance
) {
if (accountNumber == null ||
accountNumber.isBlank()) {
throw new IllegalArgumentException(
"Account number is required"
);
}
if (ownerName == null ||
ownerName.isBlank()) {
throw new IllegalArgumentException(
"Owner name is required"
);
}
if (initialBalance < 0) {
throw new IllegalArgumentException(
"Initial balance cannot be negative"
);
}
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = initialBalance;
}
public String getAccountNumber() {
return accountNumber;
}
public String getOwnerName() {
return ownerName;
}
public double getBalance() {
return balance;
}
public void deposit(double amount) {
validateAmount(amount);
balance += amount;
transactions.add(
"Deposited: " + amount
);
}
public boolean withdraw(double amount) {
validateAmount(amount);
if (amount > balance) {
return false;
}
balance -= amount;
transactions.add(
"Withdrawn: " + amount
);
return true;
}
public List<String> getTransactions() {
return List.copyOf(transactions);
}
private void validateAmount(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException(
"Amount must be positive"
);
}
}
}
150. Why This Is a Strong Example#
The class demonstrates:
private fields
final fields
constructor validation
getter methods
domain methods
private helper
collection encapsulation
defensive/unmodifiable copying
business rules
controlled state changes
This is much closer to real OOP design than simply creating getters and setters for everything.
151. Final Mental Model#
Think of a class as a small machine.
PUBLIC API
|
+-----------+-----------+
| |
read operate
| |
getters domain methods
| |
+-----------+-----------+
|
PRIVATE STATE
|
validation + rules
Outside code interacts with the machine through its API.
The internal mechanism remains under the class's control.
152. Final Summary#
Encapsulation means:
keep data and related behavior together
+
control access to internal state
+
protect valid object state
+
hide unnecessary implementation details
In Java, the most important tools are:
private
public
protected
getters
setters
final
validation
defensive copying
domain methods
The most important lesson is:
Do not just hide fields. Design the class so that the object controls its own valid state.
153. Most Important Rules to Remember#
1. Encapsulation protects and controls object state.
2. private is the most common tool for hiding fields.
3. A getter provides read access.
4. A setter provides controlled write access.
5. Not every field needs a getter.
6. Not every field needs a setter.
7. final can prevent reassignment.
8. final does not automatically make referenced objects immutable.
9. Validate constructor inputs when required.
10. Validate every state-changing operation that can violate an invariant.
11. Prefer domain methods over unrestricted setters when appropriate.
12. Do not return internal mutable collections directly unless that exposure is intentional.
13. Use defensive copies or unmodifiable results when needed.
14. Encapsulation is broader than getters and setters.
15. Encapsulation reduces coupling.
16. Encapsulation improves maintainability.
17. Encapsulation supports information hiding.
18. Encapsulation and abstraction are related but different.
19. private is not encryption or complete security.
20. A good class owns the rules for maintaining its valid state.
154. Chapter 15 Complete#
You now have the foundation needed to design safer Java objects:
Class
↓
Object
↓
State
↓
private state
↓
controlled methods
↓
validation
↓
valid object
The next chapter moves to another major OOP pillar:
Chapter 16 — Inheritance#
You will learn:
- Why inheritance?
- extends
- Parent class
- Child class
- IS-A relationship
- Single inheritance
- Multilevel inheritance
- Hierarchical inheritance
- super
- Parent constructors
- Constructor execution order
- Method inheritance
- Field inheritance
- Method overriding preview
- Access modifiers
- protected
- Object class connection
- Common inheritance mistakes
- Composition vs inheritance
- Practical programs
- Exercises
- Interview questions