Chapter 14: Encapsulation
Imagine we’re sitting together in a quiet Mumbai café — it’s evening, the rain has just stopped, and I’m going to explain encapsulation like I’m teaching my younger brother who’s just starting to understand why professional developers are so strict about it.
We’ll go super slowly, with lots of real-life analogies, complete runnable programs, step-by-step breakdowns, tables, common mistakes with fixes, and plenty of examples you can copy-paste and run right now.
Let’s dive in!
1. What is Encapsulation? (The Big Idea)
Encapsulation means wrapping data (variables) and methods (behavior) together inside a class, and controlling access to that data from outside the class.
In simple words:
- Hide the internal details (data) from the outside world
- Expose only what is necessary through public methods (getters and setters)
Real-life analogy: Think of a medicine capsule.
- The medicine (data) is hidden inside
- You only see the outer shell (public interface)
- You can swallow (use) it, but you cannot directly open it and mess with the powder inside — that’s encapsulation!
In Java, we achieve this using access modifiers and getters/setters.
2. Access Modifiers (The Four Levels of Visibility)
Java has four access modifiers that control who can see/access a field or method.
| Modifier | Visibility (Who can access?) | Real-life analogy |
|---|---|---|
| public | Everyone (any class, any package) | Public park — anyone can enter |
| protected | Same package + subclasses (even in different packages) | Family members + close friends |
| default (no keyword) | Only within the same package | Apartment building — only residents |
| private | Only within the same class | Your personal diary — only you can read |
Quick rule of thumb for beginners:
- Fields (variables) → almost always private
- Methods → public if they are part of the interface, private if they are helpers
3. Data Hiding – Why Make Fields Private?
If fields are public, anyone can directly change them — leading to bugs, invalid states, and hard-to-debug code.
Bad Example (No Encapsulation):
|
0 1 2 3 4 5 6 7 8 9 10 11 |
public class BankAccount { public double balance; // Dangerous! } BankAccount acc = new BankAccount(); acc.balance = -5000; // Negative balance? Allowed! → Bug |
Good Example (With Encapsulation):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
public class BankAccount { private double balance; // Hidden from outside // Constructor public BankAccount(double initialBalance) { if (initialBalance >= 0) { this.balance = initialBalance; } } // Getter (read access) public double getBalance() { return balance; } // Setter with validation (controlled write access) public void deposit(double amount) { if (amount > 0) { balance += amount; System.out.println("Deposited ₹" + amount + ". New balance: ₹" + balance); } else { System.out.println("Invalid deposit amount!"); } } public void withdraw(double amount) { if (amount > 0 && amount <= balance) { balance -= amount; System.out.println("Withdrew ₹" + amount + ". New balance: ₹" + balance); } else { System.out.println("Insufficient funds or invalid amount!"); } } } |
Test it:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Main { public static void main(String[] args) { BankAccount acc = new BankAccount(10000); System.out.println("Initial balance: ₹" + acc.getBalance()); acc.deposit(5000); // Works acc.withdraw(2000); // Works acc.withdraw(20000); // Blocked! // acc.balance = -1000; // ERROR! Cannot access private field directly } } |
Output:
|
0 1 2 3 4 5 6 7 8 9 |
Initial balance: ₹10000.0 Deposited ₹5000.0. New balance: ₹15000.0 Withdrew ₹2000.0. New balance: ₹13000.0 Insufficient funds or invalid amount! |
4. Getters and Setters (The Standard Way)
Getter → method to read a private field Setter → method to write/update a private field (with validation)
Naming convention (very important!):
- For field private int age;
- Getter: public int getAge()
- Setter: public void setAge(int age)
Complete Example: Student Class with Full Encapsulation
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
public class Student { private String name; private int age; private double gpa; private char grade; // Constructor public Student(String name, int age, double gpa) { setName(name); // Use setter for validation setAge(age); setGpa(gpa); } // Getters public String getName() { return name; } public int getAge() { return age; } public double getGpa() { return gpa; } public char getGrade() { return grade; } // Setters with validation public void setName(String name) { if (name != null && !name.trim().isEmpty()) { this.name = name.trim(); } else { System.out.println("Invalid name!"); } } public void setAge(int age) { if (age >= 0 && age <= 120) { this.age = age; updateGrade(); // Auto-update grade based on age } else { System.out.println("Invalid age!"); } } public void setGpa(double gpa) { if (gpa >= 0 && gpa <= 10) { this.gpa = gpa; updateGrade(); } else { System.out.println("Invalid GPA!"); } } // Private helper method private void updateGrade() { if (gpa >= 9.0) grade = 'A'; else if (gpa >= 8.0) grade = 'B'; else if (gpa >= 7.0) grade = 'C'; else if (gpa >= 6.0) grade = 'D'; else grade = 'F'; } // Public method to display info public void displayInfo() { System.out.println("Name: " + name); System.out.println("Age: " + age); System.out.println("GPA: " + gpa); System.out.println("Grade: " + grade); } } |
Test it:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
Student s = new Student("Webliance", 25, 9.2); s.displayInfo(); s.setGpa(8.5); // Valid s.setAge(150); // Invalid → blocked s.displayInfo(); |
Output:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
Name: Webliance Age: 25 GPA: 9.2 Grade: A Name: Webliance Age: 25 GPA: 8.5 Grade: B |
5. Quick Recap Table (Your Cheat Sheet)
| Concept | Best Practice / Rule | Example |
|---|---|---|
| Fields | Always private | private double balance; |
| Getters | public type getFieldName() | public double getBalance() |
| Setters | public void setFieldName(type value) + validation | public void setAge(int age) |
| public | Use for methods that are part of the public API | public void withdraw(double amount) |
| private | Use for helper methods & internal fields | private void updateGrade() |
| protected | Use when subclasses need direct access | (We’ll see more in inheritance) |
| default | Use only when package-level access is enough | Rarely used in modern code |
6. Common Mistakes & Fixes
| Mistake | Problem | Fix |
|---|---|---|
| Making fields public | Anyone can set invalid values | Make fields private, use setters |
| No validation in setters | Invalid data (negative balance, age -5) | Add checks in setters |
| Getter returns mutable object | Outside code can modify internal state | Return copy or immutable object |
| Forgetting this. in setters | Parameter shadows field | Use this.field = value; |
| No getters/setters at all | Cannot access data safely | Provide public getters & controlled setters |
7. Homework for You (Practice to Master!)
- Basic: Create Employee class with private fields (id, name, salary). Add constructors, getters, setters (validate salary > 0).
- Medium: Create Product class (name, price, quantity). Add method applyDiscount(double percent) with validation (0–100%).
- Advanced: Create Person class with private birthYear. Add getter getAge() that calculates age based on current year (use java.time.Year.now().getValue()).
- Fun: Create Temperature class with private celsius. Add getters/setters for Celsius, Fahrenheit, Kelvin (convert automatically).
- Challenge: Fix this bad code:
Java0123456789public class BadAccount {public double balance = 1000;}// How would you improve it with encapsulation?
You’re doing fantastic! Encapsulation is what makes your code safe, readable, and professional — now you’re writing real-world quality Java code.
