07. Encapsulation
Encapsulation is the OOP principle of hiding an object's **state (fields)** behind methods (getters/setters or domain methods). It prevents the object from being broken by invalid values and gives you room to change the implementation later.
What you'll learn
- 1Know `public` / `private` / package-private / `protected`
- 2Write the `private` field + getter/setter pattern
- 3Understand immutable objects and their benefits
- 4See how JDK 16+ `record` reduces boilerplate
- 5Touch on the package concept
Overview
Encapsulation is the OOP principle of hiding an object's **state (fields)** behind methods (getters/setters or domain methods). It prevents the object from being broken by invalid values and gives you room to change the implementation later.
Core Concepts
1) Access modifiers
| Modifier | Same class | Same package | Subclass | Other package |
|---|---|---|---|---|
| `public` | β | β | β | β |
| `protected` | β | β | β | β |
| (default) | β | β | β | β |
| `private` | β | β | β | β |
Most fields should be **`private`** by default.
2) getter / setter
public class Account {
private long balance;
public long getBalance() { return balance; }
public void deposit(long amount) {
if (amount <= 0) throw new IllegalArgumentException("amount > 0");
this.balance += amount;
}
}**Don't always add a setter.** If there's a meaningful **domain action** (like deposit), use it instead.
3) Immutable objects
An **immutable object** never changes after construction. They're thread-safe and easy to debug.
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) { this.x = x; this.y = y; }
public int getX() { return x; }
public int getY() { return y; }
}`final` class + `final` fields + no setters + values set only in the constructor.
4) `record` (JDK 16+)
Define an immutable data carrier in one line.
public record Point(int x, int y) {}The compiler auto-generates the constructor, accessor `x()` (not `getX()`), and `equals` / `hashCode` / `toString`.
5) Packages
Packages are **namespaces** for related classes. Declare `package com.codingnow.lecture.oop07;` at the top of the file. This lecture skips packages for simplicity; from lecture 21 onward we use them with the standard Maven layout.
Examples
Example 1 β `PrivateField.java`: first step of encapsulation
public class PrivateField {
private int age;
public int getAge() { return age; }
public void setAge(int age) {
if (age < 0) throw new IllegalArgumentException("age >= 0");
this.age = age;
}
public static void main(String[] args) {
PrivateField p = new PrivateField();
p.setAge(21);
System.out.println("age=" + p.getAge());
try {
p.setAge(-1);
} catch (IllegalArgumentException e) {
System.out.println("rejected: " + e.getMessage());
}
}
}**Output**
age=21
rejected: age >= 0**Note:** putting **validation logic** in the setter blocks invalid state up front.
Example 2 β `Account.java`: domain actions over setters
public class Account {
private long balance;
public Account(long initial) { this.balance = initial; }
public long getBalance() { return balance; }
public void deposit(long amount) {
if (amount <= 0) throw new IllegalArgumentException("amount > 0");
balance += amount;
}
public void withdraw(long amount) {
if (amount <= 0) throw new IllegalArgumentException("amount > 0");
if (amount > balance) throw new IllegalStateException("insufficient balance");
balance -= amount;
}
public static void main(String[] args) {
Account a = new Account(10_000);
a.deposit(5_000);
a.withdraw(3_000);
System.out.println("balance=" + a.getBalance());
}
}**Output**
balance=12000**Note:** domain methods like `deposit` / `withdraw` express intent much better than raw setters.
Example 3 β `ImmutablePoint.java`: immutable object
public final class ImmutablePoint {
private final int x;
private final int y;
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
public ImmutablePoint translate(int dx, int dy) {
return new ImmutablePoint(x + dx, y + dy);
}
public static void main(String[] args) {
ImmutablePoint p = new ImmutablePoint(1, 2);
ImmutablePoint q = p.translate(10, 20);
System.out.println("p=(" + p.getX() + "," + p.getY() + ")");
System.out.println("q=(" + q.getX() + "," + q.getY() + ")");
}
}**Output**
p=(1,2)
q=(11,22)**Note:** instead of mutating state, **return a new object** β the canonical immutable pattern.
Example 4 β `PointRecord.java`: a record in one line
public class PointRecord {
public record Point(int x, int y) {}
public static void main(String[] args) {
Point a = new Point(1, 2);
Point b = new Point(1, 2);
System.out.println(a);
System.out.println("a.x = " + a.x());
System.out.println("a.equals(b) = " + a.equals(b));
}
}**Output**
Point[x=1, y=2]
a.x = 1
a.equals(b) = true**Note:** `record` auto-generates `toString` / `equals` / `hashCode`.
Common Mistakes
- Reaching for a setter first β ask whether there's a meaningful domain method
- Wanting immutability but exposing the internal collection directly (shallow immutability)
- Accepting a mutable field directly in a `record` (needs a defensive copy)
- Sprinkling `public` fields everywhere β encapsulation breaks down
- Skipping validation in the setter
Summary
- Fields default to `private`; expose methods for the outside world
- Immutable objects are safe and simple
- `record` removes the boilerplate of data classes
Practice
# Practice - 07. Encapsulation
## Exercise 1 β `Temperature` encapsulation
- File: `Homework01.java`
- Key concepts: `private` field, setter validation
Requirements
- Class `Temperature` with `private double celsius`.
- `setCelsius(double)` throws `IllegalArgumentException` if below `-273.15`.
- `toFahrenheit()` converts and returns Fahrenheit.
Expected output
25.0Β°C = 77.0Β°F
rejected: -300.0Β°C is below absolute zero## Exercise 2 β `Money` record
- File: `Homework02.java`
- Key concepts: `record`, auto-generated equals/hashCode
Requirements
- Define `record Money(long amount, String currency)`.
- Two `Money` with the same amount/currency must be equal via `.equals()`.
Expected output
m1=Money[amount=1000, currency=KRW]
m1.equals(m2)=true
m1.equals(m3)=false## Solutions After trying it yourself, compare with [`answer/`](./answer/).
Solution code (homework/answer/)
answer/Homework01.java
/** Encapsulation of Temperature class. */
public class Homework01 {
public static void main(String[] args) {
Temperature t = new Temperature();
t.setCelsius(25.0);
System.out.println(t.getCelsius() + "Β°C = " + t.toFahrenheit() + "Β°F");
try {
t.setCelsius(-300.0);
} catch (IllegalArgumentException e) {
System.out.println("rejected: " + e.getMessage());
}
}
}
class Temperature {
private double celsius;
public double getCelsius() { return celsius; }
public void setCelsius(double c) {
if (c < -273.15) throw new IllegalArgumentException(c + "Β°C is below absolute zero");
this.celsius = c;
}
public double toFahrenheit() {
return celsius * 9.0 / 5.0 + 32.0;
}
}
answer/Homework02.java
/** Money record. */
public class Homework02 {
record Money(long amount, String currency) {}
public static void main(String[] args) {
Money m1 = new Money(1000, "KRW");
Money m2 = new Money(1000, "KRW");
Money m3 = new Money(1000, "USD");
System.out.println("m1=" + m1);
System.out.println("m1.equals(m2)=" + m1.equals(m2));
System.out.println("m1.equals(m3)=" + m1.equals(m3));
}
}
Try It Yourself
cd 02_oop/07_encapsulation/src
javac Account.java
java AccountNext Lecture
[08_Inheritance](../08_μμ/) β `extends`, `super`, `@Override` and method overriding.
All lecture materials and example code are openly available on GitHub.
View on GitHub β