8.2 Vererbung in Java

Die Vererbungsbeziehung wird in Java mit dem Schlüsselwort extends beschrieben. Beispiel:

class Mitarbeiter extends Person {
   /* Klassenimplementierung */
   }

Die Klasse Mitarbeiter ist eine Spezialisierung der Klasse Person. Sie erbt die öffentlichen Attribute und Methoden der Klasse Person.

Alle Klassen in Java erben direkt oder indirekt von der Java Basisklasse Object. Wird bei einer Klassendeklaration keine extends Klausel angegeben so wird die Klasse automatisch von der Klasse Object abgeleitet.

Die Klasse Object

... stellt für alle Javaklassen eine minimale Infrastruktur zur Verfügung:

  • die Möglichkeit dynamisch Objekte auf dem Heap anzulegen
  • eine Identität und die damit verbundene Möglichkeit zum Vergleich von Objekten
  • die Möglichkeit des automatischen Löschens wenn keine Referenz mehr auf eine Instanz zeigt.
  • Möglichkeit der Instrospektion (Dynamisches Erkunden der Struktur der Klasse)

Zugriff auf geerbte Methoden und Attribute

Oberklassen vererben öffentliche (public, protected) Methoden und Attribute an Unterklassen. Das heißt, dass sie wie eigene Attribute verwendet werden können.

Die Beispielklasse Person verfügt über:

  • private Attribute für Vor- und Nachnamen
  • öffentliche Methode zum Auslesen des Gesamtnamens
  • öffentliche Methode zum Setzen des Namens
  • ein öffentliches Attribut zum Verwalten des Alters
  • Einen Konstruktor zum Setzen des Namens

Die Oberklasse Person

public class Person {
    private String name;
    private String firstName;
    public int age;
    
    public Person(String ln, String fn) {
        name = ln; firstName = fn; }
    
    public Person() { this("Doe","John");}
    
    public void setName(String ln, String fn) {
        name = ln; firstName = fn; }
    public String fullName() {return (name + " " + firstName);}    
}

Die Klasse Employee soll jetzt die öffentlichen Eigenschaften der Oberklasse Person nutzen. So kann eine redundante Programmierung der schon existierenden Funktionalität vermieden werden.

Die Unterklasse Employee

public class Employee extends Person {
    private String employeeId;
    public Employee(String ln, String fn, String EmpId, int a) {
        super(ln,fn); // Java ruft hier den Konstruktor der Oberklasse auf
        employeeId = EmpId;
        age = a;
    }
    public String getEmployeeId() {return employeeId;}
    public String printAll() {
        return (
                fullName() + " " +
                employeeId + " " +
                age);}
    }

Benutzung der Klasse

Die so implementierte Klasse kann wie folgt verwendet werden:

public class MainEmployee {
    public static void main(String[] args) {
        Employee ceo = new Employee("Doe","Jane", "1", 25);
        Employee cto = new Employee("Miller","John","2", 30);

        cto.age++;
        System.out.println(ceo.printAll());
        System.out.println(cto.printAll());
    }
}

Dies ergibt die Konsolenausgabe:

Doe Jane 1 25
Miller John 2 31 

Alle öffentlichen Attribute und Methoden (Beispiel age) der Klasse Person können in der Klasse Employee genutzt werden als ob es eigene/lokale sind.

Die Vererbung funktioniert jedoch nicht rückwärts. Man kann einer Instanz von Person keine Angestelltennummer (employeeId) zuweisen. Sie existiert in der Klasse Person nicht.

"Vertragsrecht": Alle Instanzen von Employee müssen sich auch genau wie Instanzen von Person verhalten. Sie sind auch Instanzen der Klasse Person. Andere Konsumenten im Programm verlassen sich auf dieses Verhalten, diesen "Vertrag". Dies bedeutet:

  • Die Unterklasse muss eventuell erzwungene Intialisierungen der Oberklasse achten in dem Sie rekursiv deren Konstruktoren aufruft.
  • Veröffentlichte Attribute und Methoden können/sollen nicht mehr blockiert werden. Beispiel: In einem Unternehmen darf das Alter aufgrund von rechtlichen Bestimmungen nicht veröffentlicht werden

Vererbung in UML

Implementierung ohne Vererbung Implementierung mit Vererbung

Hier eine Implementierung der Klasse Employee ohne Vererbung. Die Implementierung entspricht der rechten Implementierung mit Vererbung. Die Methoden der Klasse haben hier aber direkten Zugriff auf die privaten Attribute name, firstName.

 

Eine Implementierung der Klasse Employee mit Vererbung der Klasse Person Vererbung

Vererbung privater Attribute und Methoden

Private Attribute werden nicht vererbt!

Sie sind nur innerhalb der Klasse sichtbar. Eine Unterklasse hat keinen priviligierten Zugriff auf private Methoden. Sie werden nicht vererbt. Eine Unterklasse muss wie jede andere Klasse über vorhandene öffentliche Zugriffsmethoden auf die privaten Attribute zugreifen.

Vererbung und Konstruktoren

Konstruktoren werden in Java nicht vererbt. Man Sie nur indirekt über den new() Operator für eine gegebene Klasse aufrufen.

Konstruktoren müssen für jede Unterklasse implementiert werden. Die Konstruktoren der Unterklasse müssen jedoch in der Lage sein existierende Konstruktoren der Oberklasse aufzurufen, da sie für die Initialisierung der Oberklasse notwendig sind. Hierzu wird das Schlüsselwort super() verwendet. 

Hiermit kann man jetzt die Klasse Employee() eleganter und robuster Initialisieren

public class Employee extends Person {
    private String employeeId;
    public Employee(String ln, String fn, String EmpId, int a) {
        super(fn,ln); // Java ruft hier den Konstruktor der Oberklasse auf
        employeeId = EmpId;
        age = a;
    }
    public void printAll() {
        System.out.println (
                fullName() + " " +
                employeeId + " " +
                age);
    }

Wichtig: Der Aufruf des Konstruktors der Oberklasse muss als erster Befehl im Konstruktor der Unterklasse stehen. Die Attribute der Oberklasse müssen als erstes initialisiert werden, bevor die Attribute der Unterklasse initialisiert werden. Nur so kann gewährleistet werden dass sich Instanzen der Oberklasse korrekt verhalten. Würde der Aufruf des Konstruktors der Oberklasse nicht als erstes geschehen, könnte die Unterklasse die Oberklasse inkorrekt manipulieren.

Unterklassenkonstruktoren ohne super() Aufruf

Konstruktoren müssen nicht einen bestimmten Konstruktor der Oberklasse explizit aufrufen.

Wird kein super() Aufruf implementiert, so generiert der Übersetzer automatisch einen super() Aufruf der den Standard(default)konstruktor aufruft.

Hiermit wird immer ein vollständiges Durchlaufen der Klassenhierachie beim Initialisieren gewährleistet.

Besitzt die Oberklasse nur Konstruktoren mit Übergabeparameter so muss einer dieser Konstruktoren über super() aufgerufen werden.

Im folgenden Beispiel kann man die Initialisierungskette einer Klasse C sehen die aus B abgeleitet wird. B wird aus A abgeleitet:

public class A {
    public A() {System.out.println("Constructor A called");}
}

public class B extends A{
    public B() {System.out.println("Constructor B called");}
}

public class C extends B {
    public C() {System.out.println("Constructor C called");}
}

Das erzeugen einer Instanz von C:

C c = new C();

Triggert die folgenden Ausgaben:

Constructor A called
Constructor B called
Constructor C called