Java Encapsulation

In the last lesson you learned what OOP is. So far our object fields have been wide open, so anyone could write account.balance = -5000. The first big OOP concept lets an object protect its data and control how it changes. It is called encapsulation.

πŸ€” Why protect an object’s data?

Look at this bank account with an open balance field.

class Account {
double balance; // ❌ anyone can change this directly
}
// somewhere far away in the program:
account.balance = -5000; // ❌ a negative balance, which makes no sense

Nothing stops that line. Here is why an open field hurts:

  • The field is public, so any code can write any number into it.
  • A bad value can come from anywhere. You can’t tell which line did it.
  • There are no rules. A balance should never go below zero, but nothing says so.
  • Debugging is painful. Every line that touches balance is a suspect.

We want the object to guard its own data and allow only changes that make sense. That is encapsulation.

🧩 What is encapsulation?

Encapsulation bundles two things together:

  • It keeps an object’s data and the methods that work on it in the same class.
  • It hides the data behind private access, so it can change only through those methods.

Think of a medicine capsule. You cannot grab a pinch of the powder. The shell controls how it gets out. Your object is the capsule, the data is the powder, the methods are the shell.

In Java code, encapsulation is two habits:

  • Make the fields private, so outside code cannot touch them directly.
  • Add public methods (getters and setters) to read and change the data in a controlled way.

πŸ”’ Making fields private

The private keyword locks a field down. Here is what it does:

  • Only code inside the same class can read or write the field.
  • Outside code is locked out completely.
  • This is the data-hiding part of encapsulation.

Here is the same account, but now the field is private.

class Account {
private double balance; // βœ… hidden from outside code
}
// account.balance = -5000; // ❌ ERROR now: balance has private access

That commented line no longer compiles. The mistake is caught the moment you build, not at midnight when a balance goes negative.

But a locked box with no key is useless. How does anyone deposit money or check the amount? That is what getters and setters are for.

πŸšͺ Getters and setters

To allow controlled access, we add two kinds of public methods:

  • A getter reads a field and returns its value.
  • A setter changes a field, but it can check the new value first and reject anything bad.

Here is the account with both. We try one good deposit and one bad one, then print the balance.

public class Main {
public static void main(String[] args) {
Account account = new Account();
account.deposit(1000); // βœ… valid, added
account.deposit(-500); // ❌ rejected by the setter
System.out.println("Balance: " + account.getBalance());
}
}
class Account {
private double balance;
// βœ… getter: read the balance
public double getBalance() {
return balance;
}
// βœ… setter: change the balance, but only with valid values
public void deposit(double amount) {
if (amount > 0) {
balance = balance + amount; // only positive deposits go through
} else {
System.out.println("Deposit must be positive.");
}
}
}

Here is what each piece does:

  • getBalance returns the current balance. Outside code reads only a copy, so it cannot change the field.
  • deposit is the setter. It checks amount > 0 first, then adds a good amount.
  • A bad amount is rejected and the balance does not move.
  • Outside code can no longer set balance freely. It must ask the object, and the object can say no.

Output

Deposit must be positive.
Balance: 1000.0

The standard getter/setter pattern

The common convention is a private field with a public getX() method to read it and a setX(value) method to change it. The names start with get and set followed by the field name, like getBalance and setName. Most editors can generate these methods for you from the fields, so you rarely type them by hand.

🏦 A fuller BankAccount example

Real accounts have a few fields and a few rules. Here is the plan for a bigger BankAccount:

  • The owner name is set once, then read-only. You can look at it but not change it from outside.
  • The balance grows only through deposit and shrinks only through withdraw.
  • A negative or zero deposit is rejected. A withdrawal larger than the balance is rejected too.
class BankAccount {
private String owner; // read-only after creation
private double balance; // changes only through deposit / withdraw
public BankAccount(String owner, double openingBalance) {
this.owner = owner;
if (openingBalance > 0) {
this.balance = openingBalance; // βœ… only a positive opening amount
}
}
// βœ… getter only, so owner is read-only from outside
public String getOwner() {
return owner;
}
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if (amount > 0) {
balance = balance + amount;
} else {
System.out.println("Deposit must be positive."); // ❌ bad input blocked
}
}
public void withdraw(double amount) {
if (amount <= 0) {
System.out.println("Withdrawal must be positive."); // ❌ blocked
} else if (amount > balance) {
System.out.println("Not enough money."); // ❌ blocked
} else {
balance = balance - amount; // βœ… allowed
}
}
}

Now use it. We open an account for Riya, mix valid and invalid operations, then print the result.

public class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount("Riya", 500);
account.deposit(1000); // βœ… valid
account.withdraw(2000); // ❌ more than the balance, blocked
account.withdraw(300); // βœ… valid
account.deposit(-50); // ❌ negative, blocked
System.out.println("Owner: " + account.getOwner());
System.out.println("Balance: " + account.getBalance());
}
}

Let’s trace the numbers:

  • Start with 500. The opening balance was positive, so it was accepted.
  • deposit(1000) is valid, so the balance becomes 1500.
  • withdraw(2000) asks for more than 1500, so it is blocked.
  • withdraw(300) is fine, so the balance becomes 1200.
  • deposit(-50) is negative, so it is blocked.
  • The owner has no setter, so no outside code can rename the account. That is a read-only field on purpose.

Output

Not enough money.
Deposit must be positive.
Owner: Riya
Balance: 1200.0

Every bad operation was caught by the object itself. The main method never knew the rules. It just asked, and the account decided.

🎯 Why encapsulation is worth it

Hiding data behind methods buys you real things:

  • Validation. The setter is the one gate the data must pass through, so a bad value can’t get in.
  • Control over each field. Getter plus setter means read and write. Only a getter means read-only. Only a setter means write-only, handy for a password.
  • Freedom to change the inside. Store the balance as a double today, switch to a long later. As long as getBalance returns the right number, no other code breaks.
  • Safety. No far-off line can quietly corrupt the object, because every change goes through a method you wrote.

πŸ›‘οΈ Access modifiers in one minute

An access modifier controls who can use a field or method. Java has four, from most hidden to most open:

  • private β€” only the same class. Your default for fields.
  • default (no modifier) β€” any class in the same package. A package is a folder of related classes.
  • protected β€” the same package, plus any subclass. You will meet subclasses in the next lesson.
  • public β€” any code anywhere. For the methods everyone should call, like getBalance.

The everyday rule: keep fields private, make the public face public, and start closed then open up just enough.

⚠️ Common Mistakes

Encapsulation is easy to fake. Watch for these.

Leaving fields public. A public field has no guard, so no rule can be enforced.

// ❌ wrong: anyone can write any value
public double balance;
// βœ… right: hidden, changed only through methods
private double balance;

A setter with no check. If the setter just assigns, it is the same as a public field with more typing. The check is the reason the setter exists.

// ❌ wrong: no validation, a negative age slips right through
public void setAge(int age) {
this.age = age;
}
// βœ… right: the rule lives in the setter
public void setAge(int age) {
if (age >= 0 && age <= 120) {
this.age = age;
} else {
System.out.println("Age must be between 0 and 120.");
}
}

A setter for every field by reflex. Some fields should never change from outside. A setter hands back the control you just won. When in doubt, leave it out.

// ❌ wrong: an ID should never change, yet this lets it
public void setId(int id) { this.id = id; }
// βœ… right: read-only, getter only, no setter at all
public int getId() { return id; }

βœ… Best Practices

Habits that keep encapsulation honest.

  • Make fields private by default. Open them up only through methods you chose on purpose.
  • Put the validation in the setter. Reject any value that would break the object’s rules, right there at the gate.
  • Expose only what is needed. Skip the setter to make a field read-only. Skip the getter if outsiders should not even see it.
  • Keep the rules inside the object. The object should be the single place that guards its own data, so the rule cannot be skipped or copied wrong.
  • Use this.field in setters and constructors. When the parameter has the same name as the field, this.field makes clear which is which.

🧩 What You’ve Learned

Nicely done. Let’s recap encapsulation.

  • βœ… Encapsulation bundles data with its methods and hides the data from outside code.
  • βœ… Make fields private so they cannot be changed directly.
  • βœ… Provide getters to read and setters to change data, with setters validating the value.
  • βœ… It gives you validation, control, safety, and freedom to change the inside later without breaking callers.
  • βœ… Omitting a setter makes a field read-only, which is a useful, deliberate choice.
  • βœ… Access modifiers (private, default, protected, public) decide who can use a field or method.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What does encapsulation mean?

    Why: Encapsulation bundles data with its methods, hides the data, and exposes controlled access via methods.

  2. 2

    What does the private keyword do to a field?

    Why: private means only code inside the same class can access the field directly.

  3. 3

    Why use a setter instead of a public field?

    Why: A setter can check the new value before changing the field, keeping the object's data valid.

  4. 4

    How do you make a field read-only from outside the class?

    Why: With a getter but no setter, outside code can read the value but not change it.

πŸš€ What’s Next?

Encapsulation keeps objects safe. The next big OOP idea lets one class build on another, reusing its data and behavior. That is inheritance, and it is how Java models β€œis-a” relationships, like a Dog is an Animal. Let’s learn it.

Java Inheritance

Share & Connect