Verdecken und Überladen

Verdecken von Attributen

In Java können Klassen Attribute haben, die von Subklassen (abgeleitete Klassen) geerbt werden können. Es ist jedoch möglich, dass eine Subklasse ein Attribut mit demselben Namen wie die Superklasse hat. In diesem Fall spricht man davon, dass das Attribut der Superklasse verdeckt wird.

Wenn ein Attribut der Superklasse verdeckt wird, ist das verdeckte Attribut nur in der Superklasse sichtbar und kann nicht direkt von der Subklasse verwendet werden. Stattdessen sieht die Subklasse nur ihr „eigenes“ Attribut.

Wenn eine Methode der Superklasse das verdeckte Attribut verwendet, wird das verdeckte Attribut der Superklasse verwendet und nicht das Attribut der Subklasse. Dies kann zu unerwartetem Verhalten führen, wenn die Subklasse beispielsweise das Attribut anders initialisiert oder es anders verwendet.

Um das verdeckte Attribut der Superklasse trotzdem in der Subklasse zu verwenden, kann das Schlüsselwort super verwendet werden.

Verdecken (hiding) von Attributen

  • Hat eine Subklasse ein Attribut mit dem gleichen Namen wie die Superklasse, so verdeckt sie das Attribut der Superklasse
  • Das neue Attribut wird an alle Subklassen weitergegeben
  • Methoden der Superklasse greifen weiterhin auf das alte, Methoden der Subklasse auf das neue Attribut zu
public class A {
    public int i = 5;
    public void doIt() {
        System.out.println(i);
    }
}

public class B extends A {
    public String i = "Ein String in B";
    public void doItNew() {
        System.out.println(i);
        System.out.println(", " + super.i);
    }
}
new B().doIt() -> 5
new B().doItNew() -> Ein String in B, 5

Das Verdecken ist unabhängig vom Typ der Variable und hängt nur vom Namen ab. Es ist sogar möglich ein Attribut durch Überdeckung für alle nachfolgenden Klassen zu entfernen, obwohl es in der Superklasse mit hinreichender Sichtbarkeit deklariert wurde.

public class C {
    public int i = 1;
}

public class D extends C {
    private int i; // verdeckt i aus C mit private Sichtbarkeit
}


public class E extends D {
    public void doIt() {
        i++;       // Fehler, i wurde von D mit private verdeckt und
                   // ist daher nicht mehr sichtbar.
        super.i++; // Fehler, da es in D kein sichtbares i gibt
    }
}

Wegen der vielen, doch eher überraschenden Effekte des Verdeckens von Feldern, sollte man im Allgemeinen darauf verzichten. Wenn die Variable ohnehin denselben Typ hat, gibt es normalerweise keinen Grund die Variable der Oberklasse zu verdecken. Passt der Typ nicht, ist schwer nachzuvollziehen, warum die Variablen denselben Namen tragen.

Überladen von Methoden

In Java können Methoden in einer Klasse überladen werden, indem mehrere Methoden mit demselben Namen definiert werden, aber mit unterschiedlichen Parametertypen oder -anzahlen. Das Überladen von Methoden ermöglicht es Entwickler:innen, Methoden mit ähnlicher Funktionalität zu gruppieren und gleichzeitig unterschiedliche Parameter zu akzeptieren.

Überladen (overloading) von Methoden

  • In Java dürfen mehrere Methoden einer Klasse denselben Namen haben, solange sich die Parameterlisten unterscheiden
    • Die Anzahl der Parameter ist unterschiedlich und/oder
    • Die Typen der Parameter unterscheiden sich
  • Der Rückgabetyp der Methode wird nicht beachtet
  • Der Compiler wählt die Methode aus, die von der Parameterliste her am besten passt
public void gehaltserhoehung(Geschaeftsfuehrer mi) { ... }
public void gehaltserhoehung(Manager mi) { ... }
public void gehaltserhoehung(Mitarbeiter mi) { ... }
public int average(int a1, int a2) { ... }
public int average(int a1, int a2, int a3) { ... }
public int average(int a1, int a2, int a3, int a4) { ... }

In Sprachen ohne Möglichkeit des Überladens bekommt man sehr schnell eine unansehnliche Zahl von Methoden, die alle das gleiche tun aber für verschiedenen Parameterlisten und Parametertypen gedacht sind und daher leicht unterschiedliche Namen tragen: calcDouble, calcFloat, calcInt etc. Durch das Überladen kann man der Methode einen sprechenden Namen geben und die Auswahl der passenden Methode dem Compiler überlassen.

Durch den Einsatz von überladenen Methoden kann man spezialisierte Methoden für Subklassen anbieten und so die Notwendigkeit eines Einsatzes des instanceof-Operators und von Casts deutlich reduzieren.

Vararg-Methoden (Ellipse)

Eine Vararg-Methode, ist eine Methode, die eine variable Anzahl von Argumenten akzeptiert.

Eine Vararg-Parameterliste wird mit drei Punkten ... nach dem Datentyp definiert. Der Vararg-Parameter muss immer der letzte Parameter einer Methode sein und kann nur einmal in einer Methode verwendet werden.

Die Argumente, die an eine Vararg-Methode übergeben werden, werden in der Methode in Form eines Arrays zur Verfügung gestellt.

Diese Methoden sind nützlich, wenn eine Methode eine flexible Anzahl von Argumenten akzeptieren muss, ohne dass der Aufrufer jedes Mal ein Array erstellen und übergeben muss.

Vararg-Methoden (variable arity method)

  • Gelegentlich kann man beim Schreiben der Methode nicht wissen, wie viele Parameter zu erwarten sind
  • Durch die Ellipse (…) kann man angeben, dass eine Methode beliebig viele Parameter haben kann
    public void flexibleMethode(String name, int ... attributes)
  • Die Ellipse muss als letzter Parameter angegeben werden
  • Die Methode erhält Zugriff auf die Parameter in Form eines Arrays vom angegebenen Typ (im Beispiel also int[])

Ein klassisches Beispiel für Methoden, die man nur mit Ellipse programmieren kann, sind die printf() Methoden von String bzw. PrintWriter. Bei ihnen hängt die Anzahl der zu erwartenden Parameter von dem Wert des ersten Parameters (des Format-Strings) ab. Ohne die Verwendung der Ellipse müsste man umständlich mit Object-Arrays hantieren. Die Einführung von printf war wohl einer der Hauptgründe vararg-Methoden in Java einzuführen.

Beispiel: Ellipse

public class EllipseDemo {

    public void flexibleMethode(String name, int ... attributes) {
        int summe = 0;

        for (int i = 0; i < attributes.length; i++) {
            summe += attributes[i];
        }

        System.out.println(name + " ist " + summe);
    }

    public static void main(String[] args) {
        EllipseDemo demo = new EllipseDemo();
        demo.flexibleMethode("Primsumme bis 13", 2, 3, 5, 7, 11, 13);
        demo.flexibleMethode("Gar nichts");
    }
}

Bei der Ellipse handelt es sich um eine reine Compile-Zeit-Konstruktion. D. h. der Compiler setzt sie in eine andere Konstruktion um.

Aus dem folgenden Quellcode

public void flexMethod(int ... werte) {
    for (int wert : werte) { /* tu was */ }
}

public void verwender() {
    flexMethod(3, 5, 7, 13, 21, 23);
}

wird daher nach dem Compilieren der folgende.

public void flexMethod(int[] werte) {
    for (int wert : werte) { /* tu was */ }
}

public void verwender() {
    flexMethod(new int[] { 3, 5, 7, 13, 21, 23 });
}

Namenskonvention für Methoden

In Java hat sich durch die sog. JavaBeans eine Namenskonvention für die Methoden durchgesetzt, die Attribute lesen und setzen

  • setXX - Setzen des Attributes XX
  • getXX - Lesen des Attributes XX
  • isXX - Lesen des Attributes XX wenn es boolean ist
  • hasXX - Lesen des Attributes XX wenn es boolean ist

Im Gegensatz zu z. B. C# und .NET kennt Java nicht das Konzept der Properties, d. h. von Membern einer Klasse, die zwar mit einer Zuweisung angesprochen werden können (a.x = 5), die aber durch methodenähnliche Konstrukte realisiert werden. Daher muss man in Java eine Namenskonvention verwenden und kann leider nicht auf die elegantere Zuweisung zurückgreifen und muss stattdessen eine Methodenaufruf hinschreiben (a.setX(5)).


Copyright © 2025 Thomas Smits