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:
| Modifier | Klasse | Paket | Unterklasse | Welt |
|---|---|---|---|---|
public | ✓ | ✓ | ✓ | ✓ |
protected | ✓ | ✓ | ✓ | ✗ |
| (keins) | ✓ | ✓ | ✗ | ✗ |
private | ✓ | ✗ | ✗ | ✗ |
Fokussiere dich erstmal auf:
public- Jeder kann darauf zugreifenprivate- 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 privateGetter 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()); // 0Den 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
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 contextStatic 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 onStatic 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 objectStatic 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
-
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