Dataslope logoDataslope

Encapsulation

Hiding internal state behind a small, deliberate interface — and why that one habit is the foundation of every other OOP idea

Encapsulation is the practice of hiding an object's internal state and only allowing the world to interact with it through a small, deliberate set of methods. It is the first and most important discipline of OOP. Everything else in this course — inheritance, polymorphism, composition, design patterns — only pays off if your objects are well-encapsulated.

The word itself is a metaphor. A capsule has a shell that protects what is inside. From the outside you can swallow the capsule, but you cannot reach the powder directly. An object is the same: it has a shell of methods, and the data inside is none of your business.

Why hide state at all?

Recall the procedural pain from The Software Crisis: when any function can write to any data, bugs become impossible to localize and rules become impossible to enforce. Encapsulation is the direct fix.

All access to the balance goes through the methods. Inside deposit and withdraw, the class can enforce its rules — no negative deposits, no overdrafts — once, in one place.

The two failure modes

Look at this anti-example and notice every way it can go wrong.

Code Block
Java 8 (Update 492)

There are two problems here, and they are different:

  1. No invariants are enforced. Negative balances are illegal in our domain, but OpenAccount doesn't say so. Every caller has to remember the rule.
  2. The internal representation is part of the public API. If tomorrow we decide to store the balance in cents (as an int) instead of dollars (as a double), every caller breaks.

Encapsulation fixes both at once.

Code Block
Java 8 (Update 492)

Now the rule "balance is never negative" is guaranteed by the class. There is no path through the public API that can break it. And the internal representation (double balance) can change without breaking a single caller, because callers never see it.

Access modifiers in Java

Java has four access levels that control where a member can be seen from. You will spend 95% of your time using private and public, but it is worth knowing all four.

ModifierVisible from
privateSame class only
(no modifier)Same package only (called package-private)
protectedSame package, and subclasses anywhere
publicAnywhere

The default discipline is: make everything as private as you can, and widen only when a real need appears. A field should be private unless you have a specific reason. A helper method that is only called from inside the class should be private. Constructors are often public but can be private if you want to force construction through a factory method.

Getters, setters, and the temptation to over-expose

A getter is a method that lets the outside world read a value (public double balance()). A setter is a method that lets the outside world write a value (public void setBalance(double)).

A common beginner mistake is to write a getter and a setter for every single field, and call that "encapsulation." It isn't. It mostly just makes your fields public with extra typing.

A BankAccount should not have setBalance(double). That throws away the whole point. It should have deposit and withdraw, methods named after what the world is actually allowed to do. The right question to ask when designing a class is "what should callers be able to do?" — not "what fields does it have?".

`final` fields lean on encapsulation

private final is a very common combination: the field is invisible to outsiders, and once the constructor sets it, the class itself cannot change it either. This makes the object's identity-defining data effectively immutable, which removes whole classes of bug.

A bigger encapsulation example

Let's encapsulate a slightly richer object: a Thermostat with an allowed temperature range and a current setting. Outsiders can request a new setting, but the thermostat enforces its own limits.

Code Block
Java 8 (Update 492)

Three things to notice:

  1. Thermostat has no setter that lets the caller bypass clamp. The only way to change target is through setTarget, which always clamps.
  2. clamp is private because it is internal plumbing. Callers don't need it and shouldn't see it.
  3. minF and maxF are final. The thermostat's range is part of its identity and never changes after construction.

That is a fully encapsulated object. From the outside it looks tiny; inside it protects a clear invariant.

Practice: encapsulate this!

Challenge
Java 8 (Update 492)
Encapsulate a Wallet

Implement a Wallet class.

Requirements:

  • A private field cents that starts at 0.
  • add(int cents) increases the balance. It must throw IllegalArgumentException if cents <= 0.
  • spend(int cents) returns true and decreases the balance if there is enough; otherwise it returns false and does not change the balance. It must also throw IllegalArgumentException if cents <= 0.
  • balance() returns the current balance.

Main.java runs a fixed scenario and is expected to print exactly:

spend1=true
spend2=false
balance=20

Test your understanding

QuestionSelect one

Why is it a bad idea to make a field public?

Public fields take up more memory

Public fields cannot be read

Any code anywhere can modify the field, so the class cannot enforce its own invariants

The compiler refuses to compile a class with public fields

QuestionSelect one

Which is the best sign that a class is well-encapsulated?

It has a getter and setter for every field

Its constructors throw exceptions on invalid input

Its public methods are named for behaviors callers actually need, and its fields are private

It uses the keyword final somewhere

QuestionSelect one

In the Thermostat example, why is clamp declared private?

Because Java requires helper methods to be private

Because private methods run faster

Because it is internal plumbing that callers never need to see, and hiding it keeps the public API small and intentional

Because public methods cannot call private ones

On this page