Hytale Modding
Java Grundlagen

08 - Datenkapselung und Access Modifier

Lerne, wie du deine Klassendaten schützen und kontrollieren kannst.

Bei der Datenkapselung geht es darum, die internen Details einer Klasse zu verbergen und zu kontrollieren, wie auf die Daten zugegriffen wird und wie diese verändert werden. Dies verhindert Bugs und macht deinen Code wartungsfähiger.

Das Problem ohne Datenkapselung

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! Jemand kann die Regeln brechen
        player.health = 500;      // Leben über dem Maximum!
        player.health = -50;      // Negative Leben!
        player.name = "";         // Leerer Name!
    }
}

Ohne Schutz kann jeder ungültige Werte festlegen!

Access Modifier

Java hat Schlüsselwörter, die kontrollieren, wer auf deine Klassenmitglieder zugreifen kann:

ModifierKlassePaketUnterklasseWelt
public
protected
(keins)
private

Fokussiere dich erstmal auf:

  • public - Jeder kann darauf zugreifen
  • private - Nur diese Klasse hat Zugriff

Eigenschaften privat machen

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;
    }
}

Jetzt kannst du nicht mehr direkt auf die Eigenschaften zugreifen:

Player player = new Player("Alice", 100);
player.health = 500;  // ❌ Fehler! health ist private

Getter und Setter

Erstelle getter und setter Methoden um auf private Objekte zuzugreifen:

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 - Gib den Wert aus
    public int getHealth() {
        return health;
    }
    
    // Setter - Setzt den Wert mit Bestätigung
    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;
    }
}

Jetzt kannst du sicher mit dem Objekt interagieren:

Player player = new Player("Alice", 100);

player.setHealth(150);  // Automatisch auf 100 begrenzt
System.out.println(player.getHealth());  // 100

player.setHealth(-20);  // Automatisch auf 0 gesetzt
System.out.println(player.getHealth());  // 0
Benennung der Getter und Setter

Den Java-Namenskonventionen folgend:

  • Getter: get + Eigenschaftsname (groß)
  • Setter: set + Eigenschaftsname (groß)
  • Boolean: is + Eigenschaftsname (groß)
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) { }

Vorteile der Datenkapselung

1. Validierung

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 Eigenschaften

Manchmal will man keinen Setter:

public class Monster {
    private String id;  // Soll nie verändert werden
    private int health;
    
    public Monster(String id, int health) {
        this.id = id;
        this.health = health;
    }
    
    // Nur Getter - Kein Setter!
    public String getId() {
        return id;
    }
    
    public int getHealth() {
        return health;
    }
    
    public void setHealth(int health) {
        this.health = health;
    }
}

3. Berechnete Eigenschaften

Getter müssen ein Feld nicht direkt zurückgeben:

public class Player {
    private int health;
    private int maxHealth;
    
    public int getHealth() {
        return health;
    }
    
    // Berechnete Eigenschaft
    public double getHealthPercentage() {
        return (health * 100.0) / maxHealth;
    }
    
    // Berechnete Eigenschaft
    public boolean isLowHealth() {
        return getHealthPercentage() < 25;
    }
}

Praxisbeispiele

Item mit Haltbarkeit

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!");
    }
    
    // Getter
    public String getName() {
        return name;
    }
    
    public int getDurability() {
        return durability;
    }
    
    public boolean isBroken() {
        return broken;
    }
    
    public double getDurabilityPercentage() {
        return (durability * 100.0) / maxDurability;
    }
}

Bankkonto

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;
    }
}

Geschützter-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!");
        }
    }
    
    // Nur Getter - Position und Besitzer sollten sich nicht ändern
    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;
    }
}

Wann privat und wann public verwendet wird

Allgemeine Regeln

Verwende standardmäßig private! Verwende public nur, wenn Dinge von außen zugänglich sein müssen.

private:

  • Interne Daten (Gesundheit, Position, Inventar)
  • Hilfsmethoden, die nur innerhalb der Klasse verwendet werden
  • Alles, was Validierung benötigt

public:

  • Methoden, die das Verhalten der Klasse definieren
  • Konstruktoren
  • Methoden, die andere Klassen aufrufen müssen
public class Example {
    // private - Interne Daten
    private int internalCounter;
    private String secretKey;
    
    // public - Teil des Interfaces
    public void doSomething() {
        // Uses private helper method
        validateData();
    }
    
    // private - Interne Hilfsmethode
    private void validateData() {
        // ...
    }
}

Das final Schlüsselwort

final bedeutet, dass eine Variable nicht geändert werden kann, nachdem sie gesetzt wurde:

public class Player {
    private final String id;  // Kann nach Erstellung nicht verändert werden
    private String name;      // Kann verändert werden
    private int health;       // Kann verändert werden
    
    public Player(String id, String name) {
        this.id = id;
        this.name = name;
    }
    
    public String getId() {
        return id;
    }
    
    // Kein setId() - Id ist final!
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

Das static Schlüsselwort

Static Members

A class can define two kinds of members:

  • Instance members — owned by each object (each instance has its own copy).

  • Static members — owned by the class (one shared copy for the entire type).

Put simply: instance members belong to objects; static members belong to the class itself and are shared by all objects of that type.

Declaration

    /* (access modifier) */ static ... memberName; 

Example

class Data {
    public int x; // Instanced member
    public static int y = 1000; // Static member

    // Instanced member:
    // can access to both static and non-static members
    public void foo() {
        x = 100; // OK - same as this.x = 100;
        y = 100; // OK - same as Data.y = 200;
    }

    // Static member:
    // cannot access to non-static variables
    public static void bar() {
        x = 100; // Error: non-static variable x cannot be renference from a static context
        y = 100; // OK
    }
}

Accessing static members

Data data = new Data();
data.x = 1000; // OK

data.y = 1000; // OK-ish - not really suggested; it's better to use Data.y
Data.y = 1000; // OK - best practice

Data.x = 1000; // Error: cannot access instanced variables in a static context

Static Fields

A static field represents a data member owned by the class type rather then the object. Static fields are also stored in a specific memory location that's been shared between all the object instances that are created.

It is declared as following:

/* (access modifier) (optional) */ static /* final/volatile (optional) */ fieldName;

Let's take the same Data class example and add this constructor:

public Data() {
    y++; // remember that's the same as Data.y++;
}
// Every instance of Data will have a private copy of the instanced member x
// However it will point to the same location in memory for the member y
Data d1 = new Data(); // y = 1001
d1.x = 5;
Data d2 = new Data(); // y = 1002
d2.x = 25;
Data d3 = new Data(); // y = 1003
// ... and so on

Static Methods

Static methods essentially represent a function member of a certain class type

From the Data class remember the function (instanced method) foo and (static method) bar

One can access those methods via:

Data d1 = new Data();

d1.foo(); // Instanced method: Accessible ONLY by an object

Data.bar(); // Static method: accessible without an object

Static Initializer

Use a static initializer block to run initialization logic when the class is first loaded:

class OtherData {
    private static int a = 12;
    private static int b;
    private static String msg;

    static {
        msg = "Initialization..."
        System.out.println(msg);
        b = 4;
        // ... complex initialization that can't be done in a single expression
    }
}

Übungsaufgaben

  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