W3docs

Java Kapselung

Daten und Methoden in Java-Klassen bündeln und Implementierungsdetails mit privaten Feldern und öffentlichen Zugriffsmethoden verbergen.

Kapselung ist das erste der vier Grundprinzipien der OOP und das am einfachsten umzusetzende: Die Daten einer Klasse bleiben privat, und das Verhalten wird über Methoden bereitgestellt. Die Klasse übernimmt die Verantwortung für ihren eigenen Zustand — kein externer Code kann ihn in einen ungültigen Zustand versetzen — und der Rest des Programms kommuniziert mit ihr über eine selbst kontrollierte Schnittstelle.

Der Mechanismus sind private-Felder plus public-Methoden. Das Prinzip lautet: „Expose keinen Zustand, den du nicht kontrollieren kannst."

Das Muster

Eine nicht gekapselte Klasse ist nur eine Sammlung öffentlicher Felder:

public class Account {
  public int balance;
}

Account a = new Account();
a.balance = 100;
a.balance = -50;   // nothing stops this

Die gekapselte Version verbirgt das Feld und stellt gezielte Operationen bereit:

public class Account {
  private int balance;

  public int  balance()              { return balance; }
  public void deposit(int amount) {
    if (amount <= 0) throw new IllegalArgumentException();
    balance += amount;
  }
  public boolean withdraw(int amount) {
    if (amount <= 0) throw new IllegalArgumentException();
    if (amount > balance) return false;
    balance -= amount;
    return true;
  }
}

Nun gibt es keine Möglichkeit für externen Code, balance auf einen negativen Wert zu setzen, ihn ohne Validierung zu überschreiben oder ihn ohne balance() abzufragen.

Was du gewinnst

Invarianten, denen du vertrauen kannst. Die Klasse Account erzwingt die Regel „balance ≥ 0 nach jeder öffentlichen Operation." Da nichts außerhalb auf balance zugreifen kann, lässt sich die Regel in einer einzigen Datei durchsetzen — nicht über die gesamte Codebasis.

Freiheit zur Änderung. Mit privatem balance kannst du später den Typ von int auf long wechseln, die Einheit von Dollar auf Cent ändern, es in einem BigDecimal speichern oder in eine Datenbank auslagern — ohne eine einzige aufrufende Stelle anzufassen. Mit einem öffentlichen Feld sind Typ und Speicherort in jeder Aufrufstelle festgeschrieben.

Eine kleinere, klarere API. Aufrufer sehen nur deposit, withdraw, balance — nicht die Dutzend privaten Hilfsmethoden, die dahinterstecken. Die Klasse kommuniziert was sie tut, nicht wie.

Lokalität des Denkens. Wenn etwas mit balance schiefgeht, liegt der Fehler in einer von drei Methoden — nicht in einem der tausend Orte, die das Feld sonst schreiben könnten.

Kapselung ≠ Getter und Setter für alles

Ein verbreitetes Anti-Pattern ist das mechanische Erzeugen eines Getters und eines Setters für jedes Feld:

public class Account {
  private int balance;
  public int  getBalance()           { return balance; }
  public void setBalance(int v)      { this.balance = v; }    // same as public field, with extra steps
}

Das ist technisch gesehen im Lehrbuchsinne „gekapselt", erreicht aber keinen der tatsächlichen Vorteile der Kapselung — jeder kann das Objekt noch immer in jeden beliebigen Zustand versetzen. Echte Kapselung drückt Operationen aus, keinen rohen Feldzugriff:

  • deposit(amount) statt setBalance(balance + amount)
  • withdraw(amount) mit Rückgabe von Erfolg/Misserfolg statt setBalance(balance - amount)
  • balance() (ein schreibgeschützter Accessor) ohne entsprechendes setBalance

Das nächste Kapitel über Getter und Setter erläutert die Konventionen dafür, wann welcher angemessen ist.

Defensive Kopien

Wenn der Typ eines Feldes veränderlich ist (ein Array, eine Liste, ein Date), gibt die direkte Rückgabe die Kontrolle preis:

public class Order {
  private final List<String> items = new ArrayList<>();
  public List<String> items() { return items; }      // leak!
}

Order o = new Order();
o.items().add("apple");                              // outside code mutated the order

Die Lösung ist die Rückgabe einer unveränderlichen Ansicht oder einer defensiven Kopie:

public List<String> items() {
  return List.copyOf(items);          // immutable snapshot
}

Dasselbe gilt für Setter, die veränderliche Werte entgegennehmen — kopiere sie beim Eingang:

public Order(List<String> items) {
  this.items = new ArrayList<>(items);
}

Das Kapitel über unveränderliche Klassen geht tiefer ins Detail.

Die richtige Zugriffsebene wählen

private und public sind die am häufigsten verwendeten, aber Java hat vier Ebenen, und die mittleren sind für die Kapselung relevant:

  • private — nur innerhalb derselben Klasse sichtbar. Standard für Felder und Hilfsmethoden.
  • package-private (kein Schlüsselwort) — für andere Klassen im selben Paket sichtbar. Nützlich, wenn einige kooperierende Klassen eine Einheit bilden und gegenseitig auf ihre internen Daten zugreifen sollen, die Außenwelt jedoch nicht.
  • protected — package-private plus sichtbar für Unterklassen. Für Mitglieder reservieren, die eine Unterklasse wirklich überschreiben oder darauf aufbauen muss.
  • public — überall sichtbar. Das ist deine veröffentlichte API; sobald externer Code davon abhängt, bricht eine Änderung die Aufrufer.

Die Faustregel ist das Prinzip der minimalen Sichtbarkeit: Gib jedem Mitglied den engsten Zugriff, der den Code noch funktionsfähig macht, und erweitere nur bei einem konkreten Bedarf. Ein public-Feld oder eine public-Methode ist ein Versprechen, das du halten musst. Siehe das Kapitel über Zugriffsmodifikatoren für die vollständige Tabelle.

Kapselung im Design

Ab einer gewissen Größe wird Kapselung zu einem Designwerkzeug und nicht mehr nur zu einer Codierungsregel. Jede Klasse zieht eine Grenze um einen Teil des Zustands und die Operationen darauf; der Rest des Systems kommuniziert mit ihr über diese Grenze. Die meisten Architekturmuster — geschichtet, hexagonal, MVC, Clean — sind Argumente darüber, wo die Grenzen zu ziehen sind.

Praktische Richtlinien:

  • Felder sind privat, bis das Gegenteil bewiesen ist. Beginne dort; erweitere nur mit einem Grund.
  • Verben statt Substantive exponieren. cancel(), pay(), ship() schlagen setStatus(...).
  • Keine veränderlichen Internen roh zurückgeben. Kapseln, kopieren oder eine unveränderliche Ansicht verwenden.
  • Beim Eingang validieren. Ungültigen Zustand an der Grenze ablehnen, damit interner Code von seiner Gültigkeit ausgehen kann.

Ein ausgearbeitetes Beispiel

java— editable, runs on the server

Was kommt als Nächstes

Der mechanische Teil der Kapselung — die private-Felder mit public-Methoden, die sie lesen oder schreiben — hat eigene Konventionen, die es zu kennen lohnt. Weiter zu Getter und Setter.

Übungen

Übung
Warum macht das mechanische Erzeugen eines öffentlichen Getters und eines öffentlichen Setters für jedes private Feld den Zweck der Kapselung zunichte?
Warum macht das mechanische Erzeugen eines öffentlichen Getters und eines öffentlichen Setters für jedes private Feld den Zweck der Kapselung zunichte?
Was this page helpful?