08 - Encapsulation and Access Modifiers
Learn how to protect and control access to your class data.
Encapsulation is about hiding the internal details of a class and controlling how its data is accessed and modified. This prevents bugs and makes your code more maintainable.
The Problem Without Encapsulation
public class Player {
public String name;
public int health;
public int maxHealth;
}
public class Main {
public static void main(String[] args) {
Player player = new Player();
player.health = 100;
player.maxHealth = 100;
// Oops! Someone can break the rules
player.health = 500; // Health over maximum!
player.health = -50; // Negative health!
player.name = ""; // Empty name!
}
}Without protection, anyone can set invalid values!
Access Modifiers
Java has keywords that control who can access your class members:
| Modifier | Class | Package | Subclass | World |
|---|---|---|---|---|
public | ✓ | ✓ | ✓ | ✓ |
protected | ✓ | ✓ | ✓ | ✗ |
| (none) | ✓ | ✓ | ✗ | ✗ |
private | ✓ | ✗ | ✗ | ✗ |
For now, focus on:
public- Anyone can accessprivate- Only this class can access
Making Properties Private
public class Player {
private String name;
private int health;
private int maxHealth;
public Player(String name, int maxHealth) {
this.name = name;
this.health = maxHealth;
this.maxHealth = maxHealth;
}
}Now you can't access properties directly:
Player player = new Player("Alice", 100);
player.health = 500; // ❌ Error! health is privateGetters and Setters
To access private properties, create getter and setter methods:
public class Player {
private String name;
private int health;
private int maxHealth;
public Player(String name, int maxHealth) {
this.name = name;
this.health = maxHealth;
this.maxHealth = maxHealth;
}
// Getter - returns the value
public int getHealth() {
return health;
}
// Setter - sets the value with validation
public void setHealth(int health) {
if (health < 0) {
this.health = 0;
} else if (health > maxHealth) {
this.health = maxHealth;
} else {
this.health = health;
}
}
public String getName() {
return name;
}
public int getMaxHealth() {
return maxHealth;
}
}Now you can safely interact with the object:
Player player = new Player("Alice", 100);
player.setHealth(150); // Automatically capped at 100
System.out.println(player.getHealth()); // 100
player.setHealth(-20); // Automatically set to 0
System.out.println(player.getHealth()); // 0Follow Java naming conventions:
- Getter:
get+ property name (capitalized) - Setter:
set+ property name (capitalized) - Boolean:
is+ property name (capitalized)
private int health;
public int getHealth() { }
public void setHealth(int health) { }
private boolean alive;
public boolean isAlive() { }
public void setAlive(boolean alive) { }
private String name;
public String getName() { }
public void setName(String name) { }Benefits of Encapsulation
1. Validation
public class Item {
private int durability;
private int maxDurability;
public void setDurability(int durability) {
if (durability < 0) {
this.durability = 0;
} else if (durability > maxDurability) {
this.durability = maxDurability;
} else {
this.durability = durability;
}
}
public boolean isBroken() {
return durability <= 0;
}
}2. Read-Only Properties
Sometimes you don't want a setter:
public class Monster {
private String id; // Should never change
private int health;
public Monster(String id, int health) {
this.id = id;
this.health = health;
}
// Getter only - no setter!
public String getId() {
return id;
}
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
}3. Computed Properties
Getters don't have to return a field directly:
public class Player {
private int health;
private int maxHealth;
public int getHealth() {
return health;
}
// Computed property
public double getHealthPercentage() {
return (health * 100.0) / maxHealth;
}
// Computed property
public boolean isLowHealth() {
return getHealthPercentage() < 25;
}
}Practical Examples
Item with Durability
public class Tool {
private String name;
private int durability;
private int maxDurability;
private boolean broken;
public Tool(String name, int maxDurability) {
this.name = name;
this.durability = maxDurability;
this.maxDurability = maxDurability;
this.broken = false;
}
public void use() {
if (broken) {
System.out.println(name + " is broken!");
return;
}
durability--;
System.out.println(name + " used. Durability: " + durability);
if (durability <= 0) {
broken = true;
System.out.println(name + " broke!");
}
}
public void repair() {
durability = maxDurability;
broken = false;
System.out.println(name + " repaired!");
}
// Getters
public String getName() {
return name;
}
public int getDurability() {
return durability;
}
public boolean isBroken() {
return broken;
}
public double getDurabilityPercentage() {
return (durability * 100.0) / maxDurability;
}
}Bank Account Example
public class PlayerWallet {
private int gold;
private int silver;
public PlayerWallet() {
this.gold = 0;
this.silver = 0;
}
public void addGold(int amount) {
if (amount > 0) {
gold += amount;
System.out.println("Added " + amount + " gold");
}
}
public boolean spendGold(int amount) {
if (amount > gold) {
System.out.println("Not enough gold!");
return false;
}
gold -= amount;
System.out.println("Spent " + amount + " gold");
return true;
}
public int getGold() {
return gold;
}
public int getTotalValue() {
// 1 gold = 100 silver
return gold * 100 + silver;
}
}Protected Block System
public class ProtectedBlock {
private int x, y, z;
private String type;
private String owner;
private boolean locked;
public ProtectedBlock(int x, int y, int z, String type, String owner) {
this.x = x;
this.y = y;
this.z = z;
this.type = type;
this.owner = owner;
this.locked = true;
}
public boolean canBreak(String playerName) {
if (!locked) {
return true;
}
return playerName.equals(owner);
}
public void unlock(String playerName) {
if (playerName.equals(owner)) {
locked = false;
System.out.println("Block unlocked");
} else {
System.out.println("You don't own this block!");
}
}
// Getters only - position and owner shouldn't change
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
public String getOwner() {
return owner;
}
public boolean isLocked() {
return locked;
}
}When to Use Private vs Public
Make it private by default! Only make things public if they need to be accessed from outside.
Private:
- Internal data (health, position, inventory)
- Helper methods used only within the class
- Anything that needs validation
Public:
- Methods that define the class's behavior
- Constructor
- Methods other classes need to call
public class Example {
// Private - internal data
private int internalCounter;
private String secretKey;
// Public - part of the interface
public void doSomething() {
// Uses private helper method
validateData();
}
// Private - internal helper
private void validateData() {
// ...
}
}The final Keyword
final means a variable can't be changed after it's set:
public class Player {
private final String id; // Can't change after creation
private String name; // Can change
private int health; // Can change
public Player(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
// No setId() - it's final!
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}Practice Exercises
-
Create a
BankAccountClass:- Private properties: accountNumber, balance
- Constructor to set account number
- Methods: deposit(), withdraw(), getBalance()
- Validation: can't withdraw more than balance
- Account number should be read-only
-
Create a
DoorClass:- Private properties: isLocked, keyCode
- Constructor to set the key code
- Methods: lock(), unlock(String code), isLocked()
- unlock() only works with correct code
- Code should be private (don't expose it!)
-
Create a
PlayerStatsClass:- Private properties: strength, defense, speed
- Constructor to set all stats
- Getters for all stats
- Method: getPowerLevel() that returns strength + defense + speed
- Stats can't be negative or over 100
-
Refactor a Class: Take one of your classes from the previous lesson and add proper encapsulation:
- Make all properties private
- Add appropriate getters and setters
- Add validation where needed