Autor: HM_JDSZielgruppe: Sek I

new, Konstruktoren und Ueberladen

Dieses Kapitel basiert auf dem Arbeitsblatt "Erzeugen von Objekten, Konstruktoren, Ueberladen". Es behandelt einen entscheidenden Schritt in der OOP: Von der reinen Klassenbeschreibung zur kontrollierten Erzeugung sinnvoll initialisierter Objekte.

Objekte entstehen mit new

In BlueJ kann ein Objekt per Kontextmenue erzeugt werden. Im Java-Code passiert dasselbe mit dem new-Operator.

new String();
new NatuerlicheZahl(5);

Der Operator erzeugt ein Objekt und liefert eine Referenz zurueck.

Wichtig

Wenn die Referenz nicht gespeichert wird, ist das Objekt sofort wieder unerreichbar:

new NatuerlicheZahl(5); // Objekt wird erzeugt, aber nicht weiter genutzt

Sinnvoll ist meistens:

NatuerlicheZahl nz = new NatuerlicheZahl(5);

Konstruktoren: Initialisierung beim Erzeugen

Konstruktoren sind spezielle Methoden mit drei Eigenschaften:

  1. Sie haben denselben Namen wie die Klasse.
  2. Sie haben keinen Rueckgabetyp (auch nicht void).
  3. Sie laufen automatisch beim new-Aufruf.

Beispiel:

public class NatuerlicheZahl {
    private int wert;

    public NatuerlicheZahl(int wert) {
        if (wert < 0) {
            this.wert = 0;
        } else {
            this.wert = wert;
        }
    }
}

Damit wird direkt beim Erzeugen eine Invariante gesichert: negative Werte werden abgefangen.

Default-Konstruktor

Das Arbeitsblatt weist auf eine oft uebersehene Regel hin:

  • Wenn kein Konstruktor definiert ist, fuegt Java automatisch einen parameterlosen Default-Konstruktor ein.
  • Sobald mindestens ein eigener Konstruktor vorhanden ist, gibt es diesen automatischen Default-Konstruktor nicht mehr.

Das ist ein haeufiger Grund fuer Compilerfehler bei Klassen, die spaeter erweitert werden.

Methodenaufrufe mit Punktoperator

Im Arbeitsblatt wird auch die Schreibweise komplexer Aufrufe geuebt:

(new NatuerlicheZahl(5)).addiere(new NatuerlicheZahl(7)).toString()

Der Punktoperator bindet den Aufruf an das Objekt links vom Punkt. Ohne Klammern kann die Lesbarkeit stark leiden. Deshalb gilt in laengeren Ketten:

  • lieber Zwischenergebnisse in Variablen speichern
  • Klammern bewusst fuer Klarheit einsetzen

Aufgabenidee aus dem Blatt: Bedeutung einzelner Anweisungen

Typische Analysefragen:

new NatuerlicheZahl(5);
NatuerlicheZahl nz = new NatuerlicheZahl(5);
NatuerlicheZahl nz2 = nz.addiere(nz);
NatuerlicheZahl nz3 = nz.addiere(new NatuerlicheZahl(3));

Was hier geuebt wird:

  • Unterschied zwischen "Objekt erzeugen" und "Objekt auch behalten"
  • Zusammenspiel von Variablen, Referenzen und Rueckgabewerten
  • sichere Lesart von verschachtelten Ausdruecken

Ueberladen von Methoden

Zwei Methoden mit gleichem Namen sind erlaubt, wenn sich Parameterliste unterscheidet.

public class Test {
    public void testMethode(String s) {
        System.out.println(s);
    }

    public void testMethode(int a) {
        System.out.println(a);
    }
}

Der Compiler waehlt anhand der uebergebenen Argumenttypen die passende Variante.

Nicht erlaubt

Nur der Rueckgabetyp darf sich nicht unterscheiden:

// ungueltig
public String testMethode(int a) { ... }
public int testMethode(int a) { ... }

Signaturen waeren identisch, daher Konflikt.

Ueberladene Methoden koennen sich gegenseitig nutzen

Das Arbeitsblatt zeigt auch, dass eine Ueberladung intern eine andere aufrufen kann:

public void testMethode(int a) {
    testMethode(String.valueOf(a));
}

So bleibt Logik zentralisiert und Duplikatcode wird vermieden.

Ueberladen von Konstruktoren

Konstruktoren lassen sich ebenfalls ueberladen.

public class GanzeZahl {
    private NatuerlicheZahl wert;
    private boolean nichtNegativ;

    public GanzeZahl(NatuerlicheZahl pWert, boolean pNichtNegativ) {
        wert = pWert;
        nichtNegativ = pNichtNegativ;
    }

    public GanzeZahl(int pWert) {
        this(new NatuerlicheZahl(Math.abs(pWert)), pWert >= 0);
    }
}

Entscheidende Regel aus dem Blatt:

  • this(...) muss die erste Anweisung im Konstruktor sein.

Damit wird Konstruktorverkettung sauber und konsistent.

Designregeln fuer robuste Konstruktoren

  1. Invarianten direkt im Konstruktor pruefen Objekte sollen nie ungueltig entstehen.
  2. Einen Hauptkonstruktor festlegen Andere Konstruktoren leiten auf ihn weiter.
  3. Keine uebermaessige Logik im Konstruktor Aufwendige Ablaufe besser in Fabrikmethoden oder Hilfsmethoden auslagern.
  4. Fehlerstrategie frueh festlegen Bei ungueltigen Parametern: korrigieren, Ausnahme werfen oder Standardwert setzen.

Typische Fehlerquellen

  1. this(...) nicht an erster Stelle Compilerfehler.
  2. Unklare Ueberladungen Aehnliche Signaturen koennen Aufrufe missverstaendlich machen.
  3. Objekte ohne Referenz erzeugen Instanzen sind direkt wieder verloren.
  4. Konstruktoren mit zu vielen Parametern Lesbarkeit sinkt, Vertauschungsfehler steigen.
  5. Methodenueberladen mit impliziten Konvertierungen Kann zu unerwarteter Auswahl einer anderen Variante fuehren.

Vertiefungsaufgaben

  1. Implementiere eine Klasse Bruch mit drei Konstruktoren: Bruch(), Bruch(int zaehler), Bruch(int zaehler, int nenner).
  2. Fuehre in allen Konstruktoren dieselbe Nenner-Validierung ein.
  3. Implementiere addiere(Bruch b) und teste Kettenaufrufe.
  4. Schreibe zwei ueberladene Methoden setzeWert(int) und setzeWert(String).
  5. Dokumentiere, warum bestimmte Klammern in langen Aufrufketten stehen bleiben sollten.

Transfer in das Zahlenprojekt

Die in diesem Kapitel geuebten Regeln sind direkte Vorbereitung fuer das spaetere Projekt mit NatuerlicheZahl, GanzeZahl und RationaleZahl:

  • saubere Konstruktorverkettung
  • konsistente Initialisierungsregeln
  • klare Trennung von Objektzustand und Operationen

Fazit

Das Arbeitsblatt macht deutlich:

  • new ist nicht nur Syntax, sondern der Einstieg in Lebenszyklusfragen von Objekten.
  • Konstruktoren sind der Ort, an dem Objektgueltigkeit entschieden wird.
  • Ueberladen bietet Flexibilitaet, muss aber diszipliniert gestaltet werden.

Wer diese Grundlagen sauber beherrscht, hat spaeter bei Vererbung, Polymorphie und API-Design deutlich weniger Probleme.

Konstruktoren ueberladen

Abbildung 1

Referenzen, Lebensdauer und Objektverlust

Das Arbeitsblatt weist bei new NatuerlicheZahl(5); auf einen didaktisch wichtigen Fall hin: Ein Objekt kann erzeugt werden, ohne dass es sinnvoll weiterverwendet wird.

new NatuerlicheZahl(5);

Wenn keine Referenz erhalten bleibt, ist das Objekt nach der Anweisung nicht mehr erreichbar. Fuer Einsteiger ist das ein guter Anlass, zwischen "Objekt existiert" und "Objekt ist erreichbar" zu unterscheiden.

Lesbarkeit langer Aufrufketten

Ketten wie

(new NatuerlicheZahl(5)).addiere(new NatuerlicheZahl(7)).toString();

sind technisch moeglich, aber oft schwer lesbar. Praktische Regel:

  1. Komplexe Teilschritte in Variablen auslagern.
  2. Erst danach verkettete Aufrufe verwenden.

Beispiel:

NatuerlicheZahl a = new NatuerlicheZahl(5);
NatuerlicheZahl b = new NatuerlicheZahl(7);
NatuerlicheZahl c = a.addiere(b);
System.out.println(c.toString());

Didaktisch hilft das besonders in den ersten Wochen.

Konstruktoren als Invariantenwache

Konstruktoren sind der beste Ort, um gueltige Startzustaende sicherzustellen.

Fragen, die in jedem Konstruktor beantwortet sein sollten:

  1. Welche Werte sind erlaubt?
  2. Was passiert bei ungueltigen Eingaben?
  3. Muss ein Wert normalisiert werden?
  4. Welche Felder muessen immer gesetzt sein?

Wenn diese Regeln erst spaeter in Settern "nachgezogen" werden, entsteht oft inkonsistenter Zustand.

Konstruktionsmuster fuer ueberladene Konstruktoren

Empfohlenes Muster:

  1. Ein Hauptkonstruktor mit voller Logik.
  2. Ueberladene Konstruktoren leiten mit this(...) auf den Hauptkonstruktor.

Beispiel:

public class Punkt {
    private int x;
    private int y;

    public Punkt(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public Punkt(int x) {
        this(x, 0);
    }

    public Punkt() {
        this(0, 0);
    }
}

So wird Logik nicht dupliziert.

Ueberladen robust gestalten

Bei vielen Ueberladungen steigt das Risiko von Mehrdeutigkeit. Regeln:

  • Signaturen muessen klar unterscheidbar sein.
  • Ueberladungen sollten semantisch zusammengehoeren.
  • Implizite Typkonvertierungen bedenken (int vs double).

Problembeispiel:

void setze(int x) { ... }
void setze(double x) { ... }

setze(5) waehlt int, setze(5.0) waehlt double. Das ist klar. Bei vielen Varianten wird diese Klarheit schnell schlechter.

this als Objektbezug verstehen

Im Arbeitsblatt taucht this(...) fuer Konstruktoraufrufe auf. Vertiefung:

  • this.feld greift auf Attribute des aktuellen Objekts zu.
  • this(...) ruft einen anderen Konstruktor derselben Klasse auf.

Wichtig: Beide Verwendungen haben denselben semantischen Kern, naemlich den Bezug auf das gerade konstruierte Objekt.

super(...) gegen this(...) abgrenzen

In Vererbungshierarchien gilt:

  • this(...) fuer Umleitung innerhalb derselben Klasse
  • super(...) fuer Aufruf der Oberklasse

Beide duerfen nur erste Anweisung sein. Daher kann man in einem Konstruktor nicht gleichzeitig direkt beide schreiben.

Loesung: Konstruktorverkettung so planen, dass einer den anderen indirekt erreicht.

Aufgabenset zur vertieften Anwendung

  1. Klasse Bruch mit drei Konstruktoren implementieren.
  2. Hauptkonstruktor mit voller Validierung festlegen.
  3. Alle anderen Konstruktoren auf Hauptkonstruktor umleiten.
  4. Ueberladene addiere-Methoden bauen:
    • addiere(Bruch b)
    • addiere(int ganzzahl)
  5. Testen, welche Aufrufe welche Ueberladung treffen.

Fehlerbilder in Klausuraufgaben

  1. Rueckgabetyp bei Konstruktor angegeben.
  2. this(...) nicht als erste Zeile.
  3. Erwartung, dass Oberklassenkonstruktoren vererbt werden.
  4. Ueberladung nur ueber Rueckgabetyp versucht.
  5. Objekt per new erzeugt, aber Referenz vergessen.

Wer diese fuenf Punkte erkennt, loest einen grossen Teil typischer Uebungsfehler.

Testcheckliste fuer Konstruktoren

  1. Standardfall mit gueltigen Werten.
  2. Randwerte (0, negativ, sehr gross).
  3. Alle ueberladenen Varianten.
  4. Konsistente Feldbelegung nach Konstruktion.
  5. Verhalten bei fehlerhaften Eingaben.

Diese Checkliste passt auch fuer spaetere Projektklassen.

Designheuristik fuer dieses Kapitel

Wenn der Konstruktor bereits schwer lesbar ist, fehlen oft weitere Designschritte:

  • Hilfsmethoden fuer Validierung
  • Value Objects fuer zusammengehoerige Parameter
  • Builder-Muster bei vielen optionalen Einstellungen

Fuer den Kurs reicht meist saubere Konstruktorverkettung. Die Heuristik ist aber wichtig fuer den Transfer in groessere Software.

Mini-Projekt

Entwerfe eine Klasse Temperatur mit folgenden Anforderungen:

  • interner Wert in Grad Celsius
  • Konstruktoren fuer Celsius und Kelvin
  • ungueltige Kelvinwerte abfangen
  • toString und Umrechnungsmethoden

Dabei soll mindestens eine Konstruktorueberladung auf eine andere umleiten. So wird der Blattinhalt in einem neuen Kontext gefestigt.

Fazit der Vertiefung

new, Konstruktoren und Ueberladen sind ein zusammenhaengendes Themenfeld:

  • new startet den Objektlebenszyklus
  • Konstruktoren sichern Gueltigkeit
  • Ueberladen bietet flexible, aber disziplinbeduerftige Schnittstellen

Wer diese Kombination beherrscht, kann spaetere Klassenmodelle deutlich sauberer aufbauen.

Abschlussuebung zu Konstruktoren

Implementiere eine Klasse Zeitpunkt mit den Attributen stunde, minute, sekunde.

Erzeuge drei Konstruktoren:

  1. parameterlos (00:00:00)
  2. Zeitpunkt(int stunde, int minute)
  3. Zeitpunkt(int stunde, int minute, int sekunde)

Anforderung:

  • Alle Konstruktoren sollen auf einen Hauptkonstruktor verweisen.
  • Ungueltige Werte sollen korrigiert oder abgewiesen werden.

Pruefe danach in Testfaellen, dass jeder Konstruktor einen konsistenten Zustand erzeugt. So wird Konstruktorverkettung praktisch abgesichert.

Pruefungsnahe Kurzfragen

  1. Warum ist ein Konstruktor keine normale void-Methode?
  2. Wann wird ein Default-Konstruktor automatisch erzeugt?
  3. Warum muss this(...) an erster Stelle stehen?
  4. Worin unterscheiden sich Ueberladen und Ueberschreiben?

Die Antworten auf diese vier Punkte decken den Kern des Kapitels ab.

Kurze Transferfrage

Welche Klasse aus deinen bisherigen Kapiteln wuerde am meisten von einer sauberen Konstruktorverkettung profitieren, und warum? Begruende anhand von mindestens zwei konkreten Parametervarianten und einem moeglichen Invarianzproblem.

Mini-Reflexion

Konstruktoren sind nicht nur Startcode, sondern die erste Schutzschicht fuer gueltige Objektzustaende.