Hello! Welcome back.
In our last lesson, we surveyed the four pillars of OOP, including a high-level look at encapsulation. Today, we're going to dive deep into this crucial concept. Your goal for this lesson is to learn how to apply encapsulation to protect an object's state using Java's access modifiers (public, private, and protected).
Mastering encapsulation isn't just about theory; it's a practical skill essential for Low-Level Design. In a system design interview, demonstrating that you can create robust, secure, and maintainable classes by controlling access to their internal state is a key indicator of strong design skills.
1. From Data Bundle to Protective Capsule
As a quick recap, encapsulation is about bundling an object's data (its state) and the methods that operate on that data (its behavior) into a single unit—a class.
But it's more than just bundling. The real power comes from information hiding: restricting direct access to an object's internal data and providing a controlled, public set of methods to interact with it.
To explore this idea, let's start with a short reading that uses a great real-world analogy.
This article from AlgoMaster defines encapsulation and uses the analogy of a bank account and an ATM to explain the core idea of hiding internal complexity while exposing a simple, controlled interface.
Read the sections on Encapsulation and analogy. Pay close attention to the definition: 'Encapsulation = Data hiding + Controlled access'.
The ATM analogy is perfect. You can't just walk into the bank's vault and change your balance. You must use the ATM's interface (deposit, withdraw), which has built-in rules and security. This is exactly what we want to achieve with our classes.
2. The Problem: Uncontrolled Access
Why is this control so critical? Let's see what happens when we don't enforce it.
The following video segment demonstrates a Human class where the instance variables (age, name) are directly accessible. Watch how this can lead to problems.
This clip from the Telusko channel clearly shows the problem of uncontrolled access. It's the 'before' picture that highlights why encapsulation is necessary.
Watch from the beginning to about four minutes in, covering the first example. Notice how easily the age and name variables can be set to any value from outside the Human class, without any validation or control.
As you saw, code outside the Human class could set the age to a nonsensical value, like -50. In a real system, this could lead to critical bugs, data corruption, and security vulnerabilities.
3. The Solution: Private State, Public Behavior
The solution is to enforce encapsulation using two key Java features:
privateaccess modifier: To hide the internal data.publicmethods (Getters and Setters): To provide controlled access to that data.
Let's watch the rest of that video to see how this is implemented.
Now, let's see the solution. The video demonstrates how to refactor the Human class to properly encapsulate its data.
Starting just past the four-minute mark, watch the core practice of encapsulation until the end of the video. Pay close attention to: Marking the instance variables age and name as private. Creating public 'getter' methods (getAge(), getName()) to allow read access. Creating public 'setter' methods (setAge(), setName()) to allow controlled write access.
This private fields + public getters/setters pattern is fundamental to object-oriented design in Java. By using a setter method, you create a single point of control where you can add validation logic. For example:
public class Human {
private int age;
public void setAge(int age) {
if (age >= 0 && age <= 130) { // Validation logic
this.age = age;
} else {
System.out.println("Invalid age provided.");
}
}
public int getAge() {
return this.age;
}
}
Now, the Human object protects its own state, ensuring age is always a valid number.
Test your understanding!
You are given the following Product class for an e-commerce system. Its fields are directly accessible.
public class Product {
public String name;
public double price;
public int stockQuantity;
}
Refactor this class to be properly encapsulated. Add a validation rule in the appropriate method to ensure that the price can never be set to a negative value.
Show answer
Here is the refactored, encapsulated Product class:
public class Product {
private String name;
private double price;
private int stockQuantity;
// Getter for name
public String getName() {
return this.name;
}
// Setter for name
public void setName(String name) {
this.name = name;
}
// Getter for price
public double getPrice() {
return this.price;
}
// Setter for price with validation
public void setPrice(double price) {
if (price >= 0) {
this.price = price;
} else {
System.out.println("Error: Price cannot be negative.");
}
}
// Getter for stockQuantity
public int getStockQuantity() {
return this.stockQuantity;
}
// Setter for stockQuantity
public void setStockQuantity(int stockQuantity) {
if (stockQuantity >= 0) {
this.stockQuantity = stockQuantity;
} else {
System.out.println("Error: Stock quantity cannot be negative.");
}
}
}
Notice how the setPrice and setStockQuantity methods now protect the object's state from invalid values.
4. Java's Four Access Modifiers
We've focused on private and public, but Java provides four levels of access control. Understanding all four is key to designing flexible and secure class structures. A core principle of good design is to always use the most restrictive access level possible.
The following article from Baeldung is an excellent, precise reference for all four modifiers.
Let's get precise definitions for all four access modifiers in Java. This article is a go-to reference for many Java developers.
Read sections 1 through 8. Focus on understanding the scope of each modifier: private, default (package-private), protected, and public. The summary table in section 6 is especially helpful. Section 8 provides a key best practice.
To summarize the scopes visually:

Here's a breakdown of when to use each:
private: This should be your default choice for all instance variables and internal helper methods. It ensures maximum encapsulation.default(package-private): Use this when you have helper classes or methods that are closely related and live in the same package, but you don't want them to be part of your public API. For example, classes within a data access layer might sharedefaultmethods that shouldn't be exposed to the service layer.public: Use this for methods that form the official, documented API of your class—the services it provides to the outside world. This includes constructors, getters, and setters.protected: This modifier is specifically related to inheritance. You use it when you want a method or variable to be accessible to subclasses, allowing them to extend or modify behavior, but you don't want it to be fully public. We'll see this in action in our next lesson.
Conclusion
In this lesson, you took a deep dive into encapsulation and the mechanics of how it's implemented in Java. Protecting an object's internal state is a cornerstone of robust software design.
Key Takeaways:
- Encapsulation is achieved by making fields
privateand providingpublicmethods (getters and setters) for controlled access. - This pattern allows you to add validation and logic, preventing your objects from entering an invalid state.
- Java has four access modifiers with increasing visibility:
private->default->protected->public. - A fundamental design principle is to always start with the most restrictive access level (
private) and only grant more access when necessary.
In this lesson, we briefly mentioned that the protected modifier is tied to inheritance. In our next lesson, we will fully explore this relationship as we cover how to design class hierarchies using inheritance, abstract classes, and interfaces.