Documentation In Progress
main@81c5a21
Hytale Modding
GuidesJava basics

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:

ModifierClassPackageSubclassWorld
public
protected
(none)
private

For now, focus on:

  • public - Anyone can access
  • private - 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 private

Getters 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());  // 0
Getter and Setter Naming

Follow 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

General Rules

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

  1. Create a BankAccount Class:

    • 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
  2. Create a Door Class:

    • 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!)
  3. Create a PlayerStats Class:

    • 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
  4. 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