Kapselung (Getter/Setter)
Kapselung in Java: private Felder, Getter und Setter, Validierung und warum Datenkapselung so wichtig ist. Mit Best Practices und Records.
Kapselung (Encapsulation) in Java
Stell dir vor, jeder koennte direkt auf dein Bankkonto zugreifen und den Kontostand aendern — ohne Pruefung, ohne Limit. Klingt gefaehrlich, oder? Kapselung loest genau dieses Problem: Sie schuetzt die internen Daten eines Objekts und kontrolliert den Zugriff.
Was ist Kapselung?
Kapselung bedeutet:
- Felder private machen (kein direkter Zugriff von aussen)
- Zugriff ueber Getter und Setter kontrollieren
- Validierung in den Settern durchfuehren
Ohne Kapselung — gefaehrlich
public class Bankkonto {
String inhaber;
double kontostand; // Jeder kann das aendern!
}
var konto = new Bankkonto();
konto.kontostand = -999999; // Negativer Kontostand? Kein Problem!
konto.inhaber = ""; // Leerer Name? Geht auch!
Mit Kapselung — sicher
public class Bankkonto {
private String inhaber;
private double kontostand;
public Bankkonto(String inhaber, double startguthaben) {
setInhaber(inhaber);
if (startguthaben < 0) {
throw new IllegalArgumentException("Startguthaben darf nicht negativ sein!");
}
this.kontostand = startguthaben;
}
// Getter
public String getInhaber() {
return inhaber;
}
public double getKontostand() {
return kontostand;
}
// Setter mit Validierung
public void setInhaber(String inhaber) {
if (inhaber == null || inhaber.isBlank()) {
throw new IllegalArgumentException("Inhaber darf nicht leer sein!");
}
this.inhaber = inhaber;
}
// Kein Setter fuer Kontostand! Nur ueber Methoden aenderbar.
public void einzahlen(double betrag) {
if (betrag <= 0) {
throw new IllegalArgumentException("Betrag muss positiv sein!");
}
kontostand += betrag;
}
public void abheben(double betrag) {
if (betrag <= 0) {
throw new IllegalArgumentException("Betrag muss positiv sein!");
}
if (betrag > kontostand) {
throw new IllegalArgumentException("Nicht genug Guthaben!");
}
kontostand -= betrag;
}
}
var konto = new Bankkonto("Max", 1000);
konto.einzahlen(500); // OK
konto.abheben(200); // OK
// konto.kontostand = -999; // FEHLER! kontostand ist private
// konto.abheben(50000); // IllegalArgumentException!
Getter und Setter
Konventionen
| Typ | Getter | Setter |
|---|---|---|
| Allgemein | getXyz() | setXyz(Typ wert) |
| boolean | isXyz() | setXyz(boolean wert) |
public class Person {
private String name;
private int alter;
private boolean aktiv;
// Getter
public String getName() { return name; }
public int getAlter() { return alter; }
public boolean isAktiv() { return aktiv; } // boolean: is statt get
// Setter
public void setName(String name) {
if (name != null && !name.isBlank()) {
this.name = name;
}
}
public void setAlter(int alter) {
if (alter >= 0 && alter <= 150) {
this.alter = alter;
}
}
public void setAktiv(boolean aktiv) {
this.aktiv = aktiv;
}
}
Nicht jedes Feld braucht einen Setter
Manchmal soll ein Feld nach der Erstellung nicht mehr aenderbar sein:
public class Mitarbeiter {
private final String mitarbeiterId; // Unveraenderlich!
private String name;
private double gehalt;
public Mitarbeiter(String id, String name, double gehalt) {
this.mitarbeiterId = id;
this.name = name;
this.gehalt = gehalt;
}
// Nur Getter fuer ID -- kein Setter!
public String getMitarbeiterId() { return mitarbeiterId; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getGehalt() { return gehalt; }
// Kontrollierte Gehaltsaenderung
public void gehaltsErhoehung(double prozent) {
if (prozent > 0 && prozent <= 50) {
this.gehalt *= (1 + prozent / 100);
}
}
}
Zugriffsmodifizierer im Ueberblick
| Modifizierer | Klasse | Paket | Unterklasse | Ueberall |
|---|---|---|---|---|
private | Ja | Nein | Nein | Nein |
| (kein/package) | Ja | Ja | Nein | Nein |
protected | Ja | Ja | Ja | Nein |
public | Ja | Ja | Ja | Ja |
Faustregel:
- Felder: immer
private - Getter:
public(wenn Zugriff noetig) - Setter:
publicmit Validierung (wenn Aenderung erlaubt) - Hilfsmethoden:
private(interne Logik) - API-Methoden:
public(oeffentliche Schnittstelle)
Records und Kapselung
Records bieten Kapselung ohne Boilerplate. Felder sind automatisch private final:
record Student(String name, int matrikelnummer, double note) {
// Validierung im kompakten Konstruktor
Student {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name darf nicht leer sein!");
}
if (note < 1.0 || note > 5.0) {
throw new IllegalArgumentException("Note muss zwischen 1.0 und 5.0 liegen!");
}
}
// Getter werden automatisch generiert: name(), matrikelnummer(), note()
// KEINE Setter -- Records sind unveraenderlich (immutable)!
boolean hatBestanden() {
return note <= 4.0;
}
}
var student = new Student("Max", 12345, 2.3);
System.out.println(student.name()); // Max
System.out.println(student.hatBestanden()); // true
// student.setName("Anna"); // FEHLER! Keine Setter bei Records
Vergleich mit Python
# Python: Konvention mit Unterstrich
class Bankkonto:
def __init__(self, inhaber, kontostand):
self._inhaber = inhaber # "private" (Konvention)
self.__kontostand = kontostand # Name Mangling
@property
def kontostand(self):
return self.__kontostand
@kontostand.setter
def kontostand(self, wert):
if wert >= 0:
self.__kontostand = wert
// Java: Echte Zugriffskontrolle
public class Bankkonto {
private String inhaber; // Wirklich private!
private double kontostand; // Wirklich private!
public double getKontostand() {
return kontostand;
}
// Kein Setter -- nur ueber einzahlen/abheben
}
In Python ist _private nur eine Konvention — man kann trotzdem darauf zugreifen. In Java ist private erzwungen durch den Compiler.
Immutable Objects (Unveraenderliche Objekte)
Das sicherste Muster: Objekte, die nach der Erstellung nicht mehr geaendert werden koennen:
public final class Adresse {
private final String strasse;
private final String plz;
private final String stadt;
public Adresse(String strasse, String plz, String stadt) {
this.strasse = strasse;
this.plz = plz;
this.stadt = stadt;
}
// Nur Getter, keine Setter
public String getStrasse() { return strasse; }
public String getPlz() { return plz; }
public String getStadt() { return stadt; }
// "Aenderung" erzeugt ein neues Objekt
public Adresse mitStadt(String neueStadt) {
return new Adresse(this.strasse, this.plz, neueStadt);
}
}
Oder einfacher als Record:
record Adresse(String strasse, String plz, String stadt) {
Adresse mitStadt(String neueStadt) {
return new Adresse(strasse, plz, neueStadt);
}
}
Best Practices
1. Felder immer private
// SCHLECHT:
public class Student {
public String name;
public int alter;
}
// GUT:
public class Student {
private String name;
private int alter;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
2. Validierung im Setter
public void setEmail(String email) {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("Ungueltige E-Mail!");
}
this.email = email.toLowerCase().strip();
}
3. Unveraenderliche Felder mit final
private final String id; // Wird nie geaendert
private final LocalDate erstelltAm;
4. Defensive Kopien
public class Kurs {
private final List<String> teilnehmer;
public Kurs(List<String> teilnehmer) {
// Defensive Kopie -- Aenderungen an der originalen Liste
// beeinflussen das Kurs-Objekt nicht
this.teilnehmer = new ArrayList<>(teilnehmer);
}
public List<String> getTeilnehmer() {
// Unveraenderliche Kopie zurueckgeben
return List.copyOf(teilnehmer);
}
}
Uebungen
Uebung 1: Produkt-Klasse
Erstelle eine Klasse Produkt mit gekapselten Feldern (name, preis, bestand). Validiere: Name nicht leer, Preis > 0, Bestand >= 0.
Uebung 2: Temperatur
Erstelle eine Klasse Temperatur mit einem privaten Feld celsius. Biete Getter fuer Celsius, Fahrenheit und Kelvin. Validiere im Setter.
Uebung 3: Passwort-Manager
Erstelle eine Klasse mit gekapseltem Passwort. Das Passwort soll nur ueber setPasswort() geaendert werden koennen (mit Laengen- und Komplexitaetspruefung). getPasswort() soll das Passwort nicht im Klartext zurueckgeben.
Uebung 4: Warenkorb
Erstelle eine Klasse Warenkorb mit einer privaten Liste von Produkten. Biete Methoden hinzufuegen(), entfernen(), gesamtpreis() und getProdukte() (als unveraenderliche Kopie).
Was kommt als Naechstes?
In der naechsten Lektion lernst du Enums — eine spezielle Klasse fuer fest definierte Wertegruppen wie Wochentage, Farben oder Status-Codes.
Zusammenfassung
- Kapselung schuetzt Daten durch
privateFelder und kontrollierte Zugriffsmethoden - Getter lesen Werte, Setter schreiben Werte (mit Validierung)
- Nicht jedes Feld braucht einen Setter — manche sind unveraenderlich (
final) - Records bieten automatische Kapselung ohne Boilerplate
- Immutable Objects sind die sicherste Form der Kapselung
- Defensive Kopien schuetzen interne Collections vor Manipulation
- Faustregel: Felder immer private, Zugriff nur ueber kontrollierte Methoden
Pro-Tipp: In IntelliJ generierst du Getter und Setter blitzschnell: Alt + Insert > “Getter and Setter” > Felder auswaehlen > fertig! Noch besser: Ueberlege bei jedem Feld, ob es wirklich einen Setter braucht. Oft ist ein unveraenderliches Objekt (oder ein Record) die bessere Wahl. Je weniger Setter, desto weniger Bugs!