Klassen implementieren: Konstruktor, Getter, Setter und Objekterzeugung

https://bildung.social/@oerinformatik/

https://oer-informatik.de/klassen-implementieren

Objektorientierte Programmierung, Klasse, Konstruktor, Getter und Setter, Kapselung, Pseudocode, UML-Klassendiagramm, Vererbung, Klassenattribut, Referenzsemantik

tl/dr; (ca. 27 min Lesezeit): Ein Klassendiagramm ist erst der Bauplan. Dieser Artikel zeigt, wie Du daraus Code machst: Konstruktoren, die Objekte in einen gültigen Anfangszustand bringen, Getter und Setter mit Prüfungen, Objekterzeugung mit new, Konstruktoren in Unterklassen mit super(…), fortlaufende Nummern über Klassenattribute und was bei der Übergabe von Objekten und Arrays an Methoden wirklich passiert. Alles in Pseudocode, wie er in Aufgaben üblich ist, und zum Vergleich in Java und Python [AI-Assisted]. (Zuletzt geändert am 28.10.2026)

Dieser Artikel baut auf der Kann-Liste der Artikel Grundlagen des UML-Klassendiagramms und Sichtbarkeitsmodifikatoren auf. Übungsaufgaben gibt es im Artikel Übungsaufgaben zum Implementieren von Klassen.

Schon fit in diesem Thema? Nutze die Kann-Liste und die Leitfragen unter dem Artikel als Checkliste, ob Du die relevanten Kompetenzen bereits hast.

Als Grundlagen zum Implementieren von Klassen kann ich nach Durchlesen des Artikels…

  • … aus einem UML-Klassendiagramm das Gerüst einer Klasse in Pseudocode ableiten und umgekehrt eine beschriebene Klasse mit sinnvollen Datentypen als Klassendiagramm darstellen.

  • … einen Konstruktor implementieren, der alle Attribute aus den Parametern initialisiert, und dabei erklären, warum er keinen Rückgabetyp hat und wozu this dient.

  • … Getter und Setter implementieren, einen Setter mit Gültigkeitsprüfung schreiben und begründen, wann ein Attribut nur einen Getter bekommt.

  • … Objekte mit new erzeugen, sie Variablen zuweisen, an andere Objekte übergeben und deren Methoden aufrufen, ohne auf private Attribute zuzugreifen.

  • … erläutern, warum eine Methode ohne Rückgabewert trotzdem Daten an den Aufrufer liefern kann, wenn sie ein übergebenes Objekt oder Array verändert.

  • … typische Fehler in Konstruktoren, Gettern und Settern erkennen und korrigieren.

Darüber hinaus kann ich als Expertenwissen…

  • … den Konstruktor einer Unterklasse mit super(…) implementieren und geschützte (protected) Attribute der Oberklasse nutzen.

  • … einen Konstruktor schreiben, der das neue Objekt mit this bei einem anderen Objekt anmeldet.

  • … Konstruktoren überladen, mit this(…) verketten und fortlaufende Nummern über ein Klassenattribut vergeben.

  • … Wert- und Referenzsemantik unterscheiden und vorhersagen, welche Änderungen nach einem Methodenaufruf beim Aufrufer sichtbar sind.

Im Klassendiagramm steht, was eine Klasse hat und kann: Attribute, Methoden, Sichtbarkeiten. Wie die Methoden arbeiten, steht dort nicht. In der Praxis und in Aufgaben folgt deshalb auf das Modell fast immer der nächste Schritt: Du sollst den Konstruktor, einen Getter oder Setter oder ein paar Zeilen schreiben, die Objekte erzeugen und miteinander verbinden. Diese Aufgaben sind kurz und folgen wenigen festen Regeln; wenn Du sie sicher beherrschst, schreibst Du solchen Code schnell und fehlerfrei.

Als durchgehendes Beispiel dient die Software einer Schulbibliothek mit Büchern, Fachbüchern, Leser*innen und Ausleihen.

Klassendiagramm der Schulbibliothek mit den Klassen Buch, Fachbuch, Leser und Ausleihe (mit KI erstellt)

Vom Klassendiagramm zum Klassengerüst

Jedes Element des Klassendiagramms hat eine feste Entsprechung im Code. Wenn Du diese Übersetzung beherrschst, ist das Gerüst einer Klasse in einer Minute geschrieben:

Übersetzung vom UML-Klassendiagramm in Pseudocode
Im Klassendiagramm Im Pseudocode Bedeutung
-titel: String - String titel privates Attribut, nur in der eigenen Klasse sichtbar
-verfuegbar: Boolean = true - Boolean verfuegbar = true Attribut mit Startwert
-{static} naechsteNummer: Integer = 1001 (unterstrichen) - static Integer naechsteNummer = 1001 Klassenattribut: gibt es nur einmal für alle Objekte
#seriennummer: String # String seriennummer geschützt: auch in Unterklassen sichtbar
+Buch(isbn: String, …) + Buch(String isbn, …) { … } Konstruktor: Name der Klasse, kein Rückgabetyp
+getTitel(): String + getTitel(): String { … } Methode mit Rückgabe
+setTitel(titel: String) + setTitel(String titel) { … } Methode ohne Rückgabe (in Java void)
Generalisierungspfeil von Fachbuch zu Buch class Fachbuch extends Buch Fachbuch erbt von Buch
Assoziation Ausleihe "0..*" -- "1" Buch - Buch buch in Ausleihe Eine Beziehung wird im Code zu einem Attribut, das die Referenz speichert.

Die letzte Zeile ist wichtig: Im Klassendiagramm steht ein Attribut für eine Beziehung meist nicht zusätzlich in der Klasse, im Code brauchst Du es aber. Die Klasse Ausleihe bekommt also die Attribute buch und leser, obwohl im Diagramm nur rueckgabeBis eingetragen ist.

Welche Datentypen sinnvoll sind, ergibt sich aus der Bedeutung: Bezeichnungen und Nummern mit führenden Nullen oder Bindestrichen (ISBN, Kennzeichen, Postleitzahl) sind String, Mengen und Zähler Integer, Geldbeträge und Messwerte Real (in Java double; für Geld in echter Software besser BigDecimal), Ja/Nein-Angaben Boolean, Datumswerte ein Datumstyp (Datum, in Java LocalDate), Bilder z. B. Image. Die UML-Spezifikation (UML 2.5.1, Abschnitt 21) kennt als Primitivtypen nur Integer, Real, Boolean, String und UnlimitedNatural; weitere Typen darfst Du verwenden, wenn sie vorgegeben oder eindeutig sind.

Pseudocode für Klassen

Pseudocode ist nicht genormt. Aufgaben verwenden meist eine Java-nahe Schreibweise, akzeptiert wird aber jede eindeutige Form, auch deutsche Schlüsselwörter („öffentlich“, „Rückgabe“, „Ende“) oder echter Code. In diesem Artikel gilt dieselbe Schreibweise wie im Artikel Objektorientierte Algorithmen:

Pseudocode-Bausteine für Klassen
Was Schreibweise Varianten, die ebenfalls richtig sind
Methodenkopf + setTitel(String titel) public void setTitel(String titel), + setTitel(titel: String), öffentlich setTitel(titel: String)
Methode mit Rückgabe + getTitel(): String public String getTitel(), öffentlich getTitel(): String
Zuweisung this.titel = titel this.titel := titel, this.titel ← titel
Rückgabe return titel Rückgabe titel, return this.titel
Objekt erzeugen Buch b = new Buch("978-3-16-148410-0", "Netzwerke für Einsteiger", 256) b := new Buch(…), b = neues Buch(…)
Methode aufrufen b.setVerfuegbar(false)
eigenes Objekt this self (Python)
Konstruktor der Oberklasse super(isbn, titel, seiten) base(…) (C#), „Basisklassenkonstruktor(…)“
kein Objekt null None (Python), nichts

Wichtig ist nicht die Schreibweise, sondern dass die Struktur erkennbar ist: ein vollständiger Methodenkopf mit Sichtbarkeit, Name, Parametern (mit Typ) und – falls vorhanden – Rückgabetyp, darunter die Anweisungen. Schon der richtige Methodenkopf zeigt, dass Du die Schnittstelle der Methode verstanden hast: welche Daten hineingehen und was herauskommt.

Der Konstruktor: Objekte in einen gültigen Anfangszustand bringen

Der Konstruktor (constructor) ist eine besondere Methode, die genau einmal läuft: wenn mit new ein Objekt erzeugt wird. Seine Aufgabe ist es, alle Attribute so zu belegen, dass das Objekt von Anfang an sinnvoll benutzbar ist.

+ Buch(String isbn, String titel, Integer seiten) {
    this.isbn = isbn
    this.titel = titel
    setSeiten(seiten)
}

Daran erkennst Du einen Konstruktor:

  • Er heißt wie die Klasse (in Java und C#; in Python heißt er immer __init__).

  • Er hat keinen Rückgabetyp, auch nicht void. Er gibt nichts mit return zurück. Das neue Objekt liefert new, nicht der Konstruktor. Schreibst Du in Java public void Buch(…), ist das kein Konstruktor mehr, sondern eine gewöhnliche Methode, die zufällig wie die Klasse heißt.

  • Er ist in der Regel öffentlich (+), damit andere Klassen Objekte erzeugen dürfen.

  • Er bekommt die Werte, die das Objekt von Anfang an braucht, als Parameter. Attribute mit Startwert (verfuegbar = true) muss er nicht setzen.

Wozu this?

Parameter heißen meist genauso wie die Attribute, die sie füllen. Innerhalb des Konstruktors verdeckt der Parameter dann das gleichnamige Attribut. Mit this sprichst Du ausdrücklich das Attribut des aktuellen Objekts an:

this.titel = titel      // Attribut des Objekts = Wert des Parameters
titel = titel           // Fehler: Der Parameter wird sich selbst zugewiesen, das Attribut bleibt leer.

Heißen Parameter und Attribut verschieden (setTitel(String neuerTitel)), ist this nicht nötig, schadet aber auch nicht. In Python kannst Du darauf nicht verzichten: Dort heißt es immer self.titel = titel.

Prüfungen nicht doppelt schreiben

Im Beispiel setzt der Konstruktor seiten nicht direkt, sondern ruft den Setter auf. So steht die Prüfung „Seitenzahl muss positiv sein“ nur an einer Stelle, und auch neu erzeugte Objekte können keinen ungültigen Wert bekommen. Das ist eine bewährte Regel: Gibt es einen Setter mit Prüfung, nutzt der Konstruktor ihn.

Der Standardkonstruktor

Schreibst Du gar keinen Konstruktor, ergänzen Java und C# automatisch einen parameterlosen Standardkonstruktor (default constructor), der alle Attribute auf ihren Startwert (bzw. 0, false, null) setzt. Sobald Du selbst einen Konstruktor mit Parametern schreibst, entfällt dieser automatische Konstruktor. new Buch() funktioniert dann nicht mehr, wenn Du nicht ausdrücklich auch einen parameterlosen Konstruktor anlegst.

Konstruktoren im Klassendiagramm

Im Klassendiagramm steht der Konstruktor wie eine Operation ohne Rückgabetyp: +Buch(isbn: String, titel: String, seiten: Integer). Die UML-Spezifikation kennzeichnet Konstruktoren eigentlich mit dem Stereotyp <<Create>>. Heißt der Konstruktor wie die Klasse, ist er aber auch ohne Stereotyp eindeutig, und so wird er in vielen Aufgaben und auf dieser Website notiert.

Getter und Setter

Die Attribute sind privat. Damit andere Objekte sie trotzdem lesen oder ändern können, bekommt die Klasse öffentliche Zugriffsmethoden: Getter (lesen, accessor) und Setter (schreiben, mutator).

+ getTitel(): String {
    return titel
}

+ setTitel(String titel) {
    this.titel = titel
}

Das Muster ist immer gleich. Wer Deinen Code liest, erkennt Getter und Setter an genau diesem Aufbau, und viele Werkzeuge und Bibliotheken (etwa nach der JavaBeans-Konvention) setzen ihn voraus:

Aufbau von Gettern und Settern
Getter Setter
Name get + Attributname, bei Boolean oft is…/ist… (istVerfuegbar()) set + Attributname
Parameter keine genau einer, vom Typ des Attributs
Rückgabetyp Typ des Attributs keiner (void)
Rumpf return titel this.titel = titel

Setter mit Prüfung

Der eigentliche Gewinn eines Setters ist, dass er ungültige Werte abfangen kann. Wie er reagiert, legt die Anforderung fest. Drei Varianten kommen häufig vor:

+ setSeiten(Integer seiten) {          // 1. ungültigen Wert ignorieren
    if (seiten > 0) {
        this.seiten = seiten
    }
}

+ setRabatt(Integer prozent) {         // 2. auf die nächste Grenze setzen
    if (prozent < 0) {
        this.rabatt = 0
    } else if (prozent > 50) {
        this.rabatt = 50
    } else {
        this.rabatt = prozent
    }
}

+ setEmail(String email): Boolean {    // 3. Erfolg melden
    if (not email.contains("@")) {
        return false
    }
    this.email = email
    return true
}

In echter Software wirft ein Setter bei ungültigen Werten oft eine Ausnahme (throw new IllegalArgumentException("Seitenzahl muss positiv sein")). Das zwingt den Aufrufer, sich um den Fehler zu kümmern. Verlangt eine Aufgabe nichts Bestimmtes, ist jede nachvollziehbare Variante richtig.

Nicht jedes Attribut braucht einen Setter

Getter und Setter werden nicht automatisch für jedes Attribut angelegt, sondern nur, wenn sie gebraucht werden:

  • Ein Attribut, das sich nach dem Erzeugen nie ändern darf (die ISBN eines Buchs, die Leser-Nummer), bekommt nur einen Getter. Es wird im Konstruktor gesetzt und ist danach schreibgeschützt. In Java kennzeichnet final solche Attribute.

  • Ein Attribut, das nur intern gebraucht wird (ein Zähler, eine Liste), bekommt oft gar keine Zugriffsmethode, sondern fachliche Methoden wie ausleiheHinzufuegen(a).

  • Ein Wert, der sich aus anderen ergibt, braucht kein eigenes Attribut: getAlter() berechnet das Alter aus dem Geburtsdatum, statt ein Attribut alter zu speichern, das jedes Jahr veralten würde. Im Klassendiagramm heißt so etwas abgeleitetes Attribut (/alter: Integer).

Warum nicht einfach öffentliche Attribute? Kapselung

Wäre seiten öffentlich, könnte jede Klasse buch.seiten = -5 schreiben. Das Objekt hätte keine Chance, sich zu wehren. Das Prinzip der Kapselung (encapsulation, auch Geheimnisprinzip) dreht das um: Die Daten eines Objekts sind privat, Zugriff gibt es nur über Methoden. Das bringt drei Vorteile:

  • Gültigkeit: Der Setter prüft jeden neuen Wert. Ungültige Zustände können gar nicht entstehen.

  • Kontrolle: Die Klasse entscheidet, was gelesen und was geändert werden darf (nur Getter, kein Setter).

  • Änderbarkeit: Die interne Darstellung kann sich ändern, ohne dass andere Klassen angepasst werden müssen. Speichert das Buch die Seitenzahl später anders, bleibt getSeiten() gleich.

Eine Klasse darf innerhalb ihrer eigenen Methoden direkt auf ihre privaten Attribute zugreifen, auch auf die eines anderen Objekts derselben Klasse. Von außen ist dagegen jeder direkte Zugriff ein Fehler: ausleihe.leser.name ist falsch, richtig ist ausleihe.getLeser().getName(). In Python ist „privat“ nur eine Konvention (Unterstriche); mehr dazu im Artikel zur Kapselung in Python.

Objekte erzeugen und verwenden

Ein Objekt entsteht mit new und dem Konstruktor der Klasse. Die Argumente stehen in der Reihenfolge der Parameter im Klassendiagramm:

Buch b = new Buch("978-3-16-148410-0", "Netzwerke für Einsteiger", 256)
Leser l = new Leser("Aylin Yıldız")
Ausleihe a = new Ausleihe(b, l, new Datum(2026, 11, 16))
  • new Buch(…) reserviert Speicher für ein neues Objekt, ruft den Konstruktor auf und liefert eine Referenz auf das Objekt.

  • Buch b ist eine Variable, die diese Referenz speichert. Ihr Typ ist eine Klasse (oder ein Interface).

  • Ohne new entsteht kein Objekt: Buch b = Buch(…) ist in Java ein Fehler (in Python dagegen genau richtig: b = Buch(…)).

Nach diesen drei Zeilen gibt es drei Objekte. Die Ausleihe ist über Links mit dem Buch und dem Leser verbunden, im Code also über die Referenzen in ihren Attributen buch und leser:

Objektdiagramm mit den Objekten b: Buch, a: Ausleihe und l: Leser; die Ausleihe ist mit Buch und Leser verbunden (mit KI erstellt)

Danach rufst Du Methoden über die Referenz auf und übergibst Objekte an Methoden anderer Objekte. Sehr häufig ist das Muster „Objekt aus Parametern erzeugen und einem anderen Objekt übergeben“:

+ messwertEmpfangen(Integer sensorId, Real wert, Datum zeit) {
    Messwert m = new Messwert(sensorId, wert, zeit)
    messreihe.hinzufuegen(m)
}

Die Variable m wird nach dem Methodenende nicht mehr gebraucht. Das Objekt bleibt trotzdem erhalten, weil messreihe eine Referenz darauf gespeichert hat. Ein Objekt verschwindet erst, wenn keine Referenz mehr darauf zeigt; dann räumt in Java, C# und Python die automatische Speicherbereinigung (garbage collection) auf.

Variablen vom Typ einer Oberklasse oder eines Interfaces

Der Typ der Variable muss nicht genau die Klasse des Objekts sein. Eine Variable vom Typ der Oberklasse oder eines implementierten Interfaces kann jedes passende Objekt aufnehmen:

Buch b = new Fachbuch("978-3-446-47126-2", "Datenbanken", 412, "Informatik")

Über b kannst Du alle Methoden von Buch aufrufen; welche Implementierung läuft, entscheidet das Objekt (mehr im Artikel zur Polymorphie). Diese Technik steckt auch hinter Entwurfsmustern, bei denen ein Objekt ein anderes „umhüllt“: Ein neues Objekt bekommt ein vorhandenes als Konstruktorparameter und wird derselben Variablen zugewiesen.

Quelle q = new Sensorquelle()
q = new Glaettungsfilter(q)        // der Filter umhüllt die ursprüngliche Quelle

null: kein Objekt

Eine Referenzvariable kann auch auf kein Objekt zeigen; sie hat dann den Wert null. Rufst Du darauf eine Methode auf, bricht das Programm ab (in Java mit einer NullPointerException). Wenn ein Parameter null sein darf (ein Schloss ohne Schließanlage, ein Buch ohne Fachgebiet), prüfst Du das vorher mit if (anlage != null).

Objekte, die sich selbst bekannt machen: this als Argument

this ist eine Referenz auf das aktuelle Objekt und kann wie jede andere Referenz übergeben werden. Damit kann sich ein Objekt schon im Konstruktor bei einem anderen Objekt anmelden:

+ Ausleihe(Buch buch, Leser leser, Datum rueckgabeBis) {
    this.buch = buch
    this.leser = leser
    this.rueckgabeBis = rueckgabeBis
    leser.ausleiheHinzufuegen(this)
    buch.setVerfuegbar(false)
}

Das Sequenzdiagramm zeigt die Reihenfolge der Aufrufe:

Sequenzdiagramm: Der Aufrufer erzeugt a: Ausleihe; der Konstruktor setzt die Attribute, ruft l.ausleiheHinzufuegen(this) und danach b.setVerfuegbar(false) auf (mit KI erstellt)

Nach new Ausleihe(…) kennt der Leser die neue Ausleihe, ohne dass der Aufrufer daran denken muss. Genau so registrieren sich Beobachter beim beobachteten Objekt (model.addObserver(this), siehe Beobachter-Muster). Achte auf die Reihenfolge: Erst die eigenen Attribute setzen, dann this weitergeben, denn das andere Objekt könnte sofort Methoden des neuen Objekts aufrufen.

Mehrere Konstruktoren: Überladung und this(…)

Eine Klasse darf mehrere Konstruktoren haben, wenn sich ihre Parameterlisten unterscheiden (Überladung, overloading; mehr im Artikel zum UML-Klassendiagramm). Damit der gemeinsame Code nicht doppelt geschrieben wird, ruft ein Konstruktor einen anderen mit this(…) auf:

+ Ausleihe(Buch buch, Leser leser) {
    this(buch, leser, Datum.heute().plusTage(28))    // Standard: vier Wochen
}

this(…) muss in Java bis Version 24 die erste Anweisung im Konstruktor sein. Seit Java 25 (JEP 513, „Flexible Constructor Bodies“) dürfen davor Anweisungen stehen, die nicht auf this zugreifen, etwa eine Prüfung der Parameter. Üblich und in Aufgaben erwartet bleibt this(…) am Anfang. Python kennt keine Überladung; dort löst man das mit Standardwerten für Parameter (def __init__(self, buch, leser, rueckgabe_bis=None)).

Klassenattribute: fortlaufende Nummern vergeben

Jeder Leser soll eine eindeutige Nummer ab 1001 bekommen. Ein gewöhnliches Attribut hilft nicht, denn jedes Objekt hätte sein eigenes. Ein Klassenattribut (static, im Klassendiagramm unterstrichen) gibt es dagegen nur einmal für die ganze Klasse; alle Objekte teilen es:

class Leser {
    - static Integer naechsteNummer = 1001
    - Integer leserNr
    - String name

    + Leser(String name) {
        this.name = name
        this.leserNr = naechsteNummer
        naechsteNummer = naechsteNummer + 1
    }
}

Der erste Leser bekommt 1001, der zweite 1002 und so weiter. Klassenattribute sprichst Du in der eigenen Klasse direkt an, von außen über den Klassennamen (Leser.naechsteNummer, falls öffentlich). Auf die gleiche Weise funktionieren statische Methoden wie Datum.heute().

Konstruktoren in Unterklassen: super(…)

Konstruktoren werden nicht vererbt. Eine Unterklasse braucht einen eigenen Konstruktor, und der muss dafür sorgen, dass auch der geerbte Teil des Objekts initialisiert wird. Das erledigt der Aufruf des Konstruktors der Oberklasse mit super(…):

class Fachbuch extends Buch {
    - String fachgebiet

    + Fachbuch(String isbn, String titel, Integer seiten, String fachgebiet) {
        super(isbn, titel, seiten)
        this.fachgebiet = fachgebiet
    }
}

Beim Erzeugen eines Fachbuchs initialisiert so jeder Konstruktor seinen Teil des Objekts:

Schema: new Fachbuch(…) ruft den Konstruktor von Fachbuch auf; dieser ruft zuerst mit super(…) den Konstruktor von Buch auf, der den geerbten Teil (isbn, titel, seiten) setzt, und setzt danach selbst fachgebiet (mit KI erstellt)
  • super(…) steht in Java als erste Anweisung. Er übernimmt die Parameter, die die Oberklasse braucht; danach setzt die Unterklasse ihre eigenen Attribute. Bis Java 24 ist diese Reihenfolge Pflicht. Seit Java 25 (JEP 513, „Flexible Constructor Bodies“) dürfen vor super(…) Anweisungen stehen, die nicht auf this zugreifen, etwa eine Prüfung der Parameter; eigenen Attributen darf dort höchstens ein Wert zugewiesen werden. Üblich und in Aufgaben erwartet bleibt super(…) am Anfang.

  • Die privaten Attribute der Oberklasse (isbn, titel) könnte die Unterklasse gar nicht direkt setzen. Auch deshalb führt kein Weg an super(…) vorbei.

  • Hat die Oberklasse einen parameterlosen Konstruktor, ruft Java ihn automatisch auf. Hat sie nur Konstruktoren mit Parametern, ist super(…) Pflicht.

  • In C# steht der Aufruf im Kopf (public Fachbuch(…) : base(isbn, titel, seiten)), in Python heißt er super().__init__(isbn, titel, seiten).

Auch eine abstrakte Klasse hat einen Konstruktor, obwohl man von ihr keine Objekte erzeugen kann. Er wird über super(…) aus den Unterklassen aufgerufen und initialisiert die gemeinsamen Attribute.

Geschützte Attribute (protected)

Ist ein Attribut der Oberklasse geschützt (#, protected), dürfen Methoden der Unterklasse direkt darauf zugreifen, zum Beispiel return "Fachbuch " + isbn, falls isbn geschützt wäre. Fremde Klassen dürfen das nicht (in Java gilt protected zusätzlich innerhalb desselben Pakets). Ist das Attribut privat, nutzt auch die Unterklasse den Getter. Mehr dazu im Artikel zur Vererbung.

Referenzen statt Kopien: Was bei der Übergabe passiert

Eine Variable vom Typ einer Klasse enthält nicht das Objekt selbst, sondern eine Referenz darauf. Das hat Folgen, die in Aufgaben gern abgefragt werden.

Zwei Variablen, ein Objekt

Buch a = new Buch("978-3-16-148410-0", "Netzwerke für Einsteiger", 256)
Buch b = a
b.setTitel("Netzwerke kompakt")
// a.getTitel() liefert jetzt "Netzwerke kompakt"

Buch b = a kopiert nur die Referenz. Beide Variablen zeigen auf dasselbe Objekt (Aliasing). Eine Kopie entsteht nur mit new. Aus demselben Grund vergleicht a == b bei Objekten, ob es dasselbe Objekt ist, und nicht, ob die Inhalte gleich sind; dafür gibt es equals.

Objekte und Arrays an Methoden übergeben

Übergibst Du ein Objekt oder ein Array an eine Methode, bekommt die Methode eine Kopie der Referenz. Sie arbeitet also mit demselben Objekt wie der Aufrufer. Ändert sie das Objekt (oder die Elemente des Arrays), sieht der Aufrufer die Änderung nach dem Aufruf:

+ messwerteEintragen(Real[] ziel) {
    for (Integer i = 0; i < ziel.length; i++) {
        ziel[i] = sensor.lesen()
    }
}

Real[] werte = new Real[10]
messwerteEintragen(werte)
// werte enthält jetzt zehn Messwerte, obwohl die Methode nichts zurückgibt

Das ist ein häufiges Muster bei Schnittstellen zu Geräten und Bibliotheken: Der Aufrufer stellt den Speicher bereit, die Methode füllt ihn. In Lösungen heißt das oft „Übergabe als Referenz“ (call by reference).

Bei einfachen Datentypen (Integer, Real, Boolean, in Java int, double, boolean) wird dagegen der Wert kopiert (call by value). Ändert die Methode ihren Parameter, bleibt die Variable des Aufrufers unverändert:

+ erhoehe(Integer n) {
    n = n + 1           // ändert nur die lokale Kopie
}

Integer anzahl = 5
erhoehe(anzahl)
// anzahl ist weiterhin 5

Genau genommen übergibt Java immer eine Kopie: bei einfachen Typen die Kopie des Werts, bei Objekten die Kopie der Referenz (manchmal call by sharing genannt). Das erklärt einen Sonderfall: Weist die Methode ihrem Parameter ein neues Objekt zu (ziel = new Real[10]), zeigt nur die lokale Kopie der Referenz auf das neue Array; das Array des Aufrufers bleibt unverändert. Zeichenketten (String) und Wrapper wie Integer sind in Java unveränderlich und verhalten sich deshalb wie einfache Werte. Echte Referenzparameter, bei denen auch eine neue Zuweisung beim Aufrufer ankommt, gibt es z. B. in C# (ref) und C++ (&).

Wer das veranschaulichen will, zeichnet ein Objektdiagramm: Die Variablen sind Pfeile, die Objekte Kästen. Zwei Pfeile auf denselben Kasten bedeuten ein Objekt.

Schema der drei Fälle: a und b zeigen auf dasselbe Buch-Objekt; werte und ziel zeigen auf dasselbe Array; anzahl und n sind getrennte Kopien des Werts (mit KI erstellt)

Die ganze Klasse in Java und Python

Zum Vergleich die Klasse Buch vollständig in zwei Programmiersprachen:

public class Buch {
    private final String isbn;
    private String titel;
    private int seiten;
    private boolean verfuegbar = true;

    public Buch(String isbn, String titel, int seiten) {
        this.isbn = isbn;
        this.titel = titel;
        setSeiten(seiten);
    }

    public String getIsbn() { return isbn; }

    public String getTitel() { return titel; }

    public void setTitel(String titel) { this.titel = titel; }

    public int getSeiten() { return seiten; }

    public void setSeiten(int seiten) {
        if (seiten > 0) {
            this.seiten = seiten;
        }
    }

    public boolean istVerfuegbar() { return verfuegbar; }

    public void setVerfuegbar(boolean verfuegbar) { this.verfuegbar = verfuegbar; }
}

public class Fachbuch extends Buch {
    private String fachgebiet;

    public Fachbuch(String isbn, String titel, int seiten, String fachgebiet) {
        super(isbn, titel, seiten);
        this.fachgebiet = fachgebiet;
    }

    public String getFachgebiet() { return fachgebiet; }
}

// Verwendung
Buch b = new Fachbuch("978-3-446-47126-2", "Datenbanken", 412, "Informatik");
b.setSeiten(-3);                       // wird ignoriert
System.out.println(b.getSeiten());     // 412

Typische Fehler

Typische Fehler beim Implementieren von Klassen
Fehler Beispiel Richtig
Konstruktor mit Rückgabetyp public void Buch(String isbn) public Buch(String isbn)
this vergessen, Parameter verdeckt Attribut titel = titel this.titel = titel
Getter mit Parameter oder ohne Rückgabe + getTitel(String titel) + getTitel(): String { return titel }
Setter gibt den Wert zurück oder hat keinen Parameter + setTitel(): String + setTitel(String titel)
Getter liefert den Parameter statt des Attributs + getTitel(String titel) { return titel } ohne Parameter, return this.titel
Objekt ohne new erzeugt (Java, C#) Buch b = Buch(…) Buch b = new Buch(…)
Direkter Zugriff auf private Attribute von außen b.titel = "…", a.leser.name b.setTitel("…"), a.getLeser().getName()
Argumente in falscher Reihenfolge new Buch(256, "Netzwerke", "978-…") Reihenfolge wie im Konstruktor
super(…) fehlt oder steht nicht am Anfang super(…) fehlt, obwohl Buch nur Konstruktoren mit Parametern hat; setSeiten(…) vor super(…) super(…) als erste Anweisung (seit Java 25 dürfen davor nur Anweisungen ohne Zugriff auf this stehen)
Prüfung im Konstruktor umgangen this.seiten = seiten trotz Setter mit Prüfung setSeiten(seiten) aufrufen
this weitergegeben, bevor die Attribute gesetzt sind leser.ausleiheHinzufuegen(this) als erste Zeile erst Attribute setzen, dann anmelden
Methode auf null aufgerufen anlage.hinzufuegen(this) ohne Prüfung if (anlage != null) { … }

So gehst Du bei Aufgaben vor

  • Vorgaben genau lesen: Welche Sichtbarkeit sollen Attribute und Methoden haben? Für welche Attribute sind Getter oder Setter verlangt? Sind Datentypen vorgegeben oder sollst Du sinnvolle wählen?

  • Erst der Kopf, dann der Rumpf: Sichtbarkeit, Name, Parameter mit Typen, Rückgabetyp. Ein vollständiger Methodenkopf zeigt schon, dass Du die Schnittstelle verstanden hast.

  • Konstruktor: Klassenname, kein Rückgabetyp, jedes Attribut aus einem Parameter oder Startwert setzen, bei Unterklassen zuerst super(…). Soll sich das Objekt irgendwo anmelden, zuletzt andereKlasse.anmelden(this).

  • Objekterzeugung: Konstruktor im Klassendiagramm nachsehen, Argumente in der richtigen Reihenfolge, new nicht vergessen, Ergebnis einer Variablen passenden Typs zuweisen oder direkt übergeben.

  • Erläuterungen: Fachbegriffe benutzen (Kapselung, Konstruktor, Referenz, Wert, Vererbung) und den Mechanismus beschreiben, nicht nur das Ergebnis („Das Array wird als Referenz übergeben; die Methode schreibt in dasselbe Array, das der Aufrufer angelegt hat.“).

  • Schreibweise: Jede eindeutige Pseudocode-Form ist richtig. Wichtig sind vollständige Köpfe und die richtige Logik, nicht Semikolons.

Leitfragen

  1. Wie übersetzt Du Attribute, Konstruktor, Getter und Setter aus einem UML-Klassendiagramm in Pseudocode, und welche sinnvollen Datentypen wählst Du für Nummern, Beträge und Datumswerte?

Jedes Element hat eine feste Entsprechung: -titel: String wird zu - String titel, der Konstruktor +Buch(…) zu + Buch(String isbn, …) { … } ohne Rückgabetyp, +getTitel(): String zu einer Methode mit Rückgabe, und Assoziationen werden zu Attributen, die die Referenz speichern. Nummern mit führenden Nullen oder Bindestrichen (ISBN, Postleitzahl) sind String, Geldbeträge Real (in echter Software besser BigDecimal), Datumswerte ein Datumstyp wie Datum bzw. LocalDate – siehe Abschnitt „Vom Klassendiagramm zum Klassengerüst“.

  1. Woran erkennst Du einen Konstruktor, warum hat er keinen Rückgabetyp, und wozu brauchst Du this darin?

Ein Konstruktor heißt wie die Klasse (in Python __init__), ist meist öffentlich und hat keinen Rückgabetyp, auch nicht void, weil das neue Objekt von new geliefert wird – mit void wäre es eine gewöhnliche Methode. this brauchst Du, weil gleichnamige Parameter die Attribute verdecken: this.titel = titel setzt das Attribut, titel = titel weist den Parameter sich selbst zu – siehe Abschnitt „Der Konstruktor: Objekte in einen gültigen Anfangszustand bringen“ und Abschnitt „Wozu this?“.

  1. Wie sind Getter und Setter aufgebaut, wie schreibst Du einen Setter mit Gültigkeitsprüfung, und wann bekommt ein Attribut nur einen Getter?

Ein Getter heißt get… (bei Boolean oft is…), hat keine Parameter und gibt das Attribut zurück; ein Setter heißt set…, hat genau einen Parameter vom Attributtyp und keine Rückgabe. Ein Setter mit Prüfung setzt den Wert nur, wenn er gültig ist (z. B. if (seiten > 0)), begrenzt ihn oder meldet Erfolg. Nur einen Getter bekommt ein Attribut, das sich nach dem Erzeugen nicht mehr ändern darf, etwa die ISBN – siehe Abschnitt „Getter und Setter“ und Abschnitt „Nicht jedes Attribut braucht einen Setter“.

  1. Warum sind Attribute privat, und wie greifst Du von außen auf die Daten eines Objekts zu?

Private Attribute setzen das Prinzip der Kapselung um: Setter verhindern ungültige Zustände, die Klasse kontrolliert, was gelesen und geändert werden darf, und die interne Darstellung bleibt änderbar. Von außen greifst Du nur über Methoden zu, also ausleihe.getLeser().getName() statt ausleihe.leser.name – siehe Abschnitt „Warum nicht einfach öffentliche Attribute? Kapselung“.

  1. Wie erzeugst Du ein Objekt, übergibst es einem anderen Objekt und meldest ein Objekt im Konstruktor mit this an?

Du erzeugst ein Objekt mit new und den Argumenten in der Reihenfolge des Konstruktors, z. B. Buch b = new Buch(…), und übergibst die Referenz einer Methode eines anderen Objekts (messreihe.hinzufuegen(m)). Im Konstruktor setzt Du zuerst die eigenen Attribute und meldest das neue Objekt dann mit leser.ausleiheHinzufuegen(this) an – siehe Abschnitt „Objekte erzeugen und verwenden“ und Abschnitt „Objekte, die sich selbst bekannt machen: this als Argument“.

  1. Wie sieht der Konstruktor einer Unterklasse aus, und welche Rolle spielen super(…) und protected?

Konstruktoren werden nicht vererbt; der Konstruktor der Unterklasse ruft als erste Anweisung super(…) mit den Parametern der Oberklasse auf und setzt danach seine eigenen Attribute. Private Attribute der Oberklasse kann die Unterklasse nicht direkt setzen; ist ein Attribut protected (#), dürfen Methoden der Unterklasse direkt darauf zugreifen, fremde Klassen nicht – siehe Abschnitt „Konstruktoren in Unterklassen: super(…)“ und Abschnitt „Geschützte Attribute (protected)“.

  1. Wie vergibst Du fortlaufende Nummern mit einem Klassenattribut, und wozu dient this(…)?

Ein Klassenattribut (static, im Diagramm unterstrichen) wie naechsteNummer = 1001 gibt es nur einmal für alle Objekte; der Konstruktor weist leserNr = naechsteNummer zu und erhöht danach den Zähler. this(…) ruft in einem überladenen Konstruktor einen anderen Konstruktor derselben Klasse auf, damit gemeinsamer Code nicht doppelt steht; in Java steht es als erste Anweisung (bis Java 24 Pflicht, seit Java 25 dürfen davor Anweisungen ohne Zugriff auf this stehen) – siehe Abschnitt „Klassenattribute: fortlaufende Nummern vergeben“ und Abschnitt „Mehrere Konstruktoren: Überladung und this(…)“.

  1. Warum kann eine Methode ohne Rückgabewert Daten an den Aufrufer liefern, und warum bleibt eine übergebene Zahl nach dem Aufruf unverändert?

Bei Objekten und Arrays erhält die Methode eine Kopie der Referenz und arbeitet mit demselben Objekt wie der Aufrufer, sodass er Änderungen nach dem Aufruf sieht. Bei einfachen Datentypen wird dagegen der Wert kopiert (call by value); ändert die Methode ihren Parameter, bleibt die Variable des Aufrufers unverändert – siehe Abschnitt „Objekte und Arrays an Methoden übergeben“.


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Klassen implementieren: Konstruktor, Getter, Setter und Objekterzeugung” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/klassen-implementieren veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/programmierung/programmiergrundlagen. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 28.10.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: