1.2 Encapsulation & Access Modifiers
← Back to Chapter 1: OOPs Concepts
Encapsulation is the bundling of data (attributes) and the methods that manipulate that data into a single operational unit, while restricting direct access to an object's internal representation.
1. Why Encapsulation Matters
- Invariable Integrity: Guarantees that internal fields cannot be set to invalid or corrupt states from arbitrary external code.
- Implementation Decoupling: You can modify internal data storage (e.g., changing an array to an
ArrayList) without breaking external consumer contracts. - Auditability & Observability: Allows logging, validation, security checks, or thread synchronization inside getters/setters.
2. Java Access Modifiers Matrix
Java has 4 distinct levels of visibility:
| Modifier | Same Class | Same Package | Subclass (Different Pkg) | Everywhere (World) |
|---|---|---|---|---|
private | ✅ | ❌ | ❌ | ❌ |
| (default / package-private) | ✅ | ✅ | ❌ | ❌ |
protected | ✅ | ✅ | ✅ | ❌ |
public | ✅ | ✅ | ✅ | ✅ |
Avoid Public Mutable Fields
Public fields expose internal pointers directly to mutations. Always mark fields private (and final whenever feasible).
3. Defensive Copying: Preventing Leakage
A common vulnerability occurs when returning mutable references (such as java.util.Date or a java.util.List):
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class Department {
private final String name;
private final List<String> employees;
public Department(String name, List<String> employees) {
this.name = name;
// Defensive Copy on Input: Prevents caller from mutating our internal list
this.employees = new ArrayList<>(employees);
}
public String getName() {
return name;
}
// Defensive Copy on Output: Return unmodifiable view or clone
public List<String> getEmployees() {
return Collections.unmodifiableList(employees);
}
}