Encapsulation and Data Hiding

Difficulty: Beginner

Question

What is encapsulation? How is it different from data hiding, and why are public fields considered bad practice?

Answer

Let me explain encapsulation by starting with a problem it solves, because the definition alone sounds abstract until you see what goes wrong without it.

Imagine a BankAccount class with a public balance field. Any part of the program can write account.balance = -50000. There is no check, no log, and if the rule ever changes, say withdrawals now need a minimum balance, you would have to hunt through the whole codebase to fix every place that touches the field. The class cannot defend itself.

Encapsulation solves this by bundling the data and the methods that operate on it into a single unit (the class), and controlling access to that data. You make the fields private and provide public methods such as deposit and withdraw, which validate every change. The object becomes responsible for keeping itself in a valid state, and the rest of the world interacts through a small, stable public interface.

What about data hiding? Data hiding is the mechanism, encapsulation is the broader principle. Hiding the field with the private modifier is data hiding. Encapsulation also includes grouping behaviour with data and deciding what the public surface should look like. Many interviewers treat the two as almost synonyms, but if you get asked the difference, that is the nuance to offer: data hiding restricts access, encapsulation packages state and behaviour together and often uses data hiding to do it.

There is a subtle trap here. Making a field private and then generating a public getter and setter for it does not give you real encapsulation. If every private field has a blind setter, you have just written a public field with extra typing. True encapsulation exposes behaviour, not raw state. Instead of setBalance, you offer deposit and withdraw. Instead of getItems returning the internal list, you return an unmodifiable view or a copy, because otherwise a caller can mutate your internals through the returned reference.

Benefits you can list: control over how data is changed (validation), flexibility to change the internal representation without breaking callers (for example, switching balance from double to BigDecimal), easier debugging since only a few methods can modify state, thread-safety opportunities through synchronising those methods, and the ability to make classes read-only by omitting setters.

The strongest form of encapsulation is immutability. A class with private final fields, no setters, and defensive copies of mutable inputs can never be in an invalid state after construction. Java's String and the wrapper classes work exactly this way.

In C++, the same idea uses private and protected sections, and in Python it is convention based: a leading underscore means "treat as internal", and double underscore triggers name mangling, but nothing is truly enforced by the language.

Code examples

A self-defending BankAccount

class BankAccount {
    private double balance;                 // hidden state

    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("Deposit must be positive");
        balance += amount;
    }

    public void withdraw(double amount) {
        if (amount > balance) throw new IllegalStateException("Insufficient funds");
        balance -= amount;
    }

    public double getBalance() { return balance; }
}

public class EncapsulationDemo {
    public static void main(String[] args) {
        BankAccount acc = new BankAccount();
        acc.deposit(500);
        acc.withdraw(200);
        System.out.println(acc.getBalance());
        try {
            acc.withdraw(1000);
        } catch (IllegalStateException e) {
            System.out.println(e.getMessage());
        }
    }
}

The only way to change balance is through methods that enforce the business rules.

Leaking internals through a getter

import java.util.*;

class Team {
    private final List<String> members = new ArrayList<>(List.of("Asha", "Ravi"));

    List<String> leaky() { return members; }                              // exposes internal list
    List<String> safe()  { return Collections.unmodifiableList(members); } // read-only view
}

public class LeakDemo {
    public static void main(String[] args) {
        Team t = new Team();
        t.leaky().add("Intruder");
        System.out.println(t.safe());
        try {
            t.safe().add("Another");
        } catch (UnsupportedOperationException e) {
            System.out.println("Blocked");
        }
    }
}

Returning the raw list breaks encapsulation even though the field is private.

Key points

Concepts covered

encapsulation, data-hiding, getters-setters, immutability