Java course menu
Java Encapsulation: private Fields, Getters, Setters and Access Modifiers
Learn encapsulation in Java: access modifiers (private, default, protected, public), private fields with getters and setters, validation, class invariants, immutable classes, and defensive copies of mutable fields.
On this page
Encapsulation means keeping an object's data private and allowing changes only through methods that keep the object valid. Outside code sees what an object can do, not how it stores its data. That makes objects hard to misuse, and lets you change their internals without breaking the code that uses them.
Access modifiers#
The usual rule: make fields private, and expose public methods for the operations that make sense.
Private fields with validated methods#
class BankAccount {
private final String owner;
private double balance;
BankAccount(String owner) {
this.owner = owner;
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit must be positive");
}
balance += amount;
}
public boolean withdraw(double amount) {
if (amount <= 0 || amount > balance) {
return false;
}
balance -= amount;
return true;
}
public double getBalance() {
return balance;
}
public String getOwner() {
return owner;
}
}
public class Main {
public static void main(String[] args) {
BankAccount acc = new BankAccount("Asha");
acc.deposit(1000);
System.out.println(acc.withdraw(300) + " " + acc.withdraw(5000));
System.out.println(acc.getOwner() + ": " + acc.getBalance());
// acc.balance = 1_000_000; // error: balance has private access
}
}true false
Asha: 700.0The balance can now change only through deposit and withdraw, which enforce the rules. The rule "the balance is never negative" is a class invariant, and encapsulation is what makes it impossible to break from outside.
Getters and setters#
A getter (getBalance(), or isActive() for booleans) reads a value; a setter (setName(...)) changes it. They are a common Java convention, and many frameworks expect them.
Be careful not to add a setter for every field automatically. setBalance(double) would let any code set any balance and make the validation pointless. Prefer methods that describe real operations: deposit, withdraw, ship, cancel.
When a setter is appropriate, it should validate:
class Thermostat {
private double temperature = 20;
public double getTemperature() {
return temperature;
}
public void setTemperature(double value) {
if (value < 10 || value > 30) {
throw new IllegalArgumentException(value + " is outside 10-30");
}
temperature = value;
}
}
public class Main {
public static void main(String[] args) {
Thermostat t = new Thermostat();
t.setTemperature(22);
System.out.println(t.getTemperature());
try {
t.setTemperature(45);
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
System.out.println(t.getTemperature());
}
}22.0
Error: 45.0 is outside 10-30
22.0Hiding the representation#
Because callers use methods rather than fields, you can change how data is stored without changing any calling code:
class Duration {
private int seconds; // could later become minutes, or java.time.Duration
Duration(int hours, int minutes) {
this.seconds = hours * 3600 + minutes * 60;
}
public int getHours() { return seconds / 3600; }
public int getMinutes() { return seconds % 3600 / 60; }
public void addMinutes(int m) { seconds += m * 60; }
}
public class Main {
public static void main(String[] args) {
Duration d = new Duration(1, 50);
d.addMinutes(25);
System.out.println(d.getHours() + "h " + d.getMinutes() + "m");
}
}2h 15mImmutable classes#
The strongest form of encapsulation is an immutable object, whose state can never change after construction. String, Integer and LocalDate are immutable. To make your own:
- make the class
final(no subclasses can add mutable state); - make all fields
private final; - provide no setters; "changing" methods return a new object;
- defensively copy any mutable data passed in or returned.
final class Money {
private final long paise;
Money(long paise) {
this.paise = paise;
}
Money plus(Money other) {
return new Money(this.paise + other.paise); // a new object
}
@Override
public String toString() {
return String.format("Rs %d.%02d", paise / 100, paise % 100);
}
}
public class Main {
public static void main(String[] args) {
Money price = new Money(129950);
Money fee = new Money(705);
Money total = price.plus(fee);
System.out.println(price + " + " + fee + " = " + total);
}
}Rs 1299.50 + Rs 7.05 = Rs 1306.55Immutable objects are safe to share between methods and threads. Java's records create simple immutable data classes in one line; see Records and Enums.
Defensive copies#
If a class stores a mutable object (such as a List) and returns it directly, callers can change the class's private data through it. Return an unmodifiable view or a copy:
import java.util.ArrayList;
import java.util.List;
class Course {
private final List<String> students = new ArrayList<>();
public void enroll(String name) {
students.add(name);
}
public List<String> getStudents() {
return List.copyOf(students); // read-only copy
}
}
public class Main {
public static void main(String[] args) {
Course c = new Course();
c.enroll("Asha");
List<String> list = c.getStudents();
try {
list.add("Intruder");
} catch (UnsupportedOperationException e) {
System.out.println("Cannot modify the course from outside");
}
System.out.println(c.getStudents());
}
}Cannot modify the course from outside
[Asha]Common mistakes#
Practice#
Practice exercise
Write a class Stock with a private quantity that can never go negative, methods receive(int n) and boolean dispatch(int n), and a getter. Receive 50, dispatch 30, try to dispatch 40, and print the results.
Show solution
class Stock {
private int quantity;
public void receive(int n) {
if (n > 0) quantity += n;
}
public boolean dispatch(int n) {
if (n <= 0 || n > quantity) return false;
quantity -= n;
return true;
}
public int getQuantity() {
return quantity;
}
}
public class Warehouse {
public static void main(String[] args) {
Stock s = new Stock();
s.receive(50);
System.out.println(s.dispatch(30) + " " + s.dispatch(40));
System.out.println(s.getQuantity());
}
}true false
20Key takeaway#
Finished this lesson?