Command Palette

Search for a command to run...

Level 1 · 15 min

Encapsulation

Encapsulation is the first pillar of OOP. It bundles data and the methods that operate on that data within a single unit (class), and restricts direct access to some of the object's components. It's the primary mechanism for maintaining class invariants.

Access Modifiers

private: accessible only within the class. package-private (default): accessible within the same package. protected: accessible within the package and by subclasses. public: accessible from anywhere. Rule: use the most restrictive modifier that works. Default to private for fields; expose behavior through public methods, not data.

Information Hiding

Information hiding means exposing only what callers need and hiding implementation details. This reduces coupling — callers don't depend on how data is stored internally, only on the contract (method signatures). Example: a Temperature class can store internally in Celsius and expose getCelsius(), getFahrenheit(), getKelvin() — the storage format is hidden. Bloch's Item 15 (Effective Java) states: "The single most important factor that distinguishes a well-designed component from a poorly designed one is the degree to which the component hides its internal data and other implementation details from other components." Crucially, Item 17 extends this to mutability: an immutable class achieves the strongest encapsulation — no mutators, all fields final and private. String and BigDecimal are canonical examples in the JDK. Martin reinforces the same point (Clean Code, Chapter 12): "Hiding implementation is not just a matter of putting a layer of functions between the variables. Hiding implementation is about abstractions! A class does not simply push its variables out through getters and setters. Rather, it exposes abstract interfaces that allow its users to manipulate the essence of the data, without having to know how that data is implemented."

Invariant Protection

Class invariants are conditions that must always be true for a valid object. Encapsulation enforces invariants by ensuring only the class itself can modify its state. If balance is always >= 0, a BankAccount class with a private balance field and a public debit(amount) method that checks amount <= balance maintains this invariant. Direct field access (public balance) cannot guarantee it.

Key Takeaways

  • Encapsulation = data + behavior in one unit, restricted access. Not just setters and getters.
  • Getters/setters for every field is not encapsulation — it's exposing state with extra steps.
  • Design classes around behavior ('what can this object do?'), not data ('what data does it hold?').

Code example

// Bad encapsulation: public fields, no invariant protection
class BankAccount {
  public double balance;  // direct access — anyone can set to -1000
  public String owner;
}

// Good encapsulation: invariants enforced
class BankAccount {
  private final String owner;
  private double balance;

  BankAccount(String owner, double initialDeposit) {
    if (initialDeposit < 0) throw new IllegalArgumentException("Negative deposit");
    this.owner = owner;
    this.balance = initialDeposit;
  }

  void debit(double amount) {
    if (amount <= 0) throw new IllegalArgumentException();
    if (amount > balance) throw new InsufficientFundsException();
    this.balance -= amount;
  }

  void credit(double amount) {
    if (amount <= 0) throw new IllegalArgumentException();
    this.balance += amount;
  }

  double getBalance() { return balance; }  // read-only access
}