DEV Community

Arijit Banerjee
Arijit Banerjee

Posted on

Mastering Encapsulation in Java

Introduction

Object-Oriented Programming (OOP) is built on four core principles:

  • Encapsulation
  • Inheritance
  • Polymorphism
  • Abstraction

In this article, we'll explore Encapsulation, one of the most important concepts in Java, using a simple Expense Tracker application.


Understanding Encapsulation with a Real-Life Analogy

Imagine you're at a coffee shop.

If your money is kept in an open cash drawer on the counter, anyone can:

  • Take money out
  • Add fake money
  • Accidentally damage it

You have no control over your cash.

A safer approach is keeping your money inside a wallet.

When the barista asks for $5, they don't reach into your pocket. Instead:

  1. They request the money.
  2. You verify the request.
  3. You hand over the correct amount.

This gives you complete control over how your money is accessed and modified.

Encapsulation works the same way in programming.


What is Encapsulation?

Encapsulation is the process of:

  • Bundling data (variables) and behavior (methods) into a single unit (class).
  • Restricting direct access to internal data.
  • Providing controlled access through methods.

In Java, encapsulation is achieved by:

  1. Declaring variables as private.
  2. Providing public getter and setter methods.

Why Do We Need Encapsulation?

Consider an Expense Tracker application.

Without encapsulation, anyone could set an expense amount to a negative value:

expense.amount = -500;
Enter fullscreen mode Exit fullscreen mode

An expense of -500 doesn't make sense and can corrupt our application's data.

Encapsulation helps prevent such invalid operations.


Expense Class Example

public class Expense {

    // Private fields
    private String description;
    private double amount;

    // Constructor
    public Expense(String description, double amount) {
        this.description = description;
        setAmount(amount);
    }

    // Getter for description
    public String getDescription() {
        return description;
    }

    // Setter for description
    public void setDescription(String description) {
        this.description = description;
    }

    // Getter for amount
    public double getAmount() {
        return amount;
    }

    // Setter with validation
    public void setAmount(double amount) {
        if (amount < 0) {
            throw new IllegalArgumentException(
                "Amount cannot be negative!"
            );
        }
        this.amount = amount;
    }
}
Enter fullscreen mode Exit fullscreen mode

How Validation Protects Data

The setAmount() method contains validation logic:

if (amount < 0) {
    throw new IllegalArgumentException(
        "Amount cannot be negative!"
    );
}
Enter fullscreen mode Exit fullscreen mode

Now:

expense.setAmount(-10);
Enter fullscreen mode Exit fullscreen mode

will throw an exception instead of storing invalid data.

This ensures the integrity of our Expense Tracker.


Interview Spotlight: Getters, Setters, and Encapsulation

Question

Why do we encapsulate data if we are just providing public getters and setters anyway? Isn't that the same as public variables?

Answer

No.

Public variables allow unrestricted modification of data.

Getters and setters provide control over how data is accessed and modified.

Benefits include:

1. Validation

if (amount < 0) {
    throw new IllegalArgumentException();
}
Enter fullscreen mode Exit fullscreen mode

Invalid values can be blocked before they enter the system.

2. Read-Only Fields

Provide only a getter and omit the setter.

public String getId() {
    return id;
}
Enter fullscreen mode Exit fullscreen mode

Now the value can be viewed but not modified.

3. Flexible Internal Implementation

Today:

private double amount;
Enter fullscreen mode Exit fullscreen mode

Tomorrow:

private int amountInCents;
Enter fullscreen mode Exit fullscreen mode

External code still uses:

expense.getAmount();
Enter fullscreen mode Exit fullscreen mode

without any changes.

This makes applications easier to maintain and evolve.


Active Recall Challenge

Let's enhance our Expense Tracker by adding a category field.

Requirements:

  • The field should be private.
  • The field should have a getter.
  • The field should have a setter.
  • The setter should reject:
    • null
    • Empty strings ("")
    • Strings containing only spaces

If the value is invalid, it should throw an IllegalArgumentException.

Solution

private String category;

public String getCategory() {
    return category;
}

public void setCategory(String category) {
    if (category == null || category.trim().isEmpty()) {
        throw new IllegalArgumentException(
            "Category cannot be null or empty!"
        );
    }
    this.category = category;
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Encapsulation hides internal data from direct access.
  • Private fields protect an object's state.
  • Getters and setters provide controlled access to data.
  • Validation inside setters helps maintain clean and reliable data.
  • Encapsulation improves security, maintainability, and flexibility.

By mastering encapsulation, you build safer and more robust Java applications.

Top comments (0)