• Keine Ergebnisse gefunden

Das Konzept der Vererbung

Im Dokument Einführung in Java (Seite 53-63)

Aufgabe 2.3 class Point {

3 OOP II : Vererbung

3.1 Das Konzept der Vererbung

Eine Klasse kann ihre Daten und Methoden an eine andere Klasse "vererben" (Bild 3.1)

Transportmittel

Landtransportmittel

"ist ein"-Beziehung Oberklasse (superclass)

Abgeleitete Klasse(subclass)

Bild 3.1 Oberklasse und abgeleitete Klasse

Die vererbende Klasse wird Oberklasse (superclass) genannt, die erbende Klasse bezeichnet man als abgeleitete Klasse (subclass). Es ist üblich, dass der Vererbung eine "ist ein"-Beziehung zugrunde liegt: ein Landtransportmittel ist ein Transportmittel. Wenn man den Pfeil in diesem Sinn liest, ist seine Richtung sinnvoll.

Die Vererbung ist nicht auf zwei Klassen beschränkt. Bild 3.2 zeigt ein umfangreicheres Beispiel.

− Die Klasse "Transportmittel" enthält Daten und Methoden, die allen Transportmitteln gemeinsam sind.

− Von dieser Klasse wird "Landtransportmittel" abgeleitet, und es werden die für diese Teilmenge der Transportmittel notwendigen Daten und Methoden hinzugefügt.

− Ein "Auto" ist ein "Landtransportmittel". Die Klasse "Landtransportmittel" stellt also aus Sicht der Klasse "Auto" die Oberklasse dar.

− Die Klasse "Amphibienfahrzeug" erbt Daten und Methoden von "Auto" und von

"Wassertransportmittel". Ein Amphibienfahrzeug ist ein Auto, und ein Amphibien-fahrzeug ist ein Wassertransportmittel. In Java ist Mehrfachvererbung nicht zulässig.

Eine Oberklasse stellt eine Generalisierung der von ihr abgeleiteten Klassen dar. "Landtrans-portmittel" enthält z.B. für "Rikscha", "Fahrrad" und "Auto" gemeinsame Daten und Methoden. Jede abgeleitete Klasse fügt dann für sie typische Dinge hinzu oder legt das geerbte Verhalten neu fest.

Eine abgeleitete Klasse ist eine Spezialisierung ihrer Oberklasse. "Rikscha", "Fahrrad" und

"Auto" sind z.B. spezielle "Landfahrzeuge". In der jeweiligen Klasse brauchen nur noch die Abweichungen beschrieben zu werden, alles andere kann man wiederverwenden.

Transportmittel breite

hoehe bewegen()

Landtransportmittel radzahl

fahren() schieben()

Wassertransportmittel bruttoregistertonnen

anlegen() ablegen()

Auto spritverbrauch

starten() fahren() tanken() Fahrrad

...

...

Amphibienfahrzeug hersteller schottenDichtmachen()

Klasse Daten Methoden

Rikscha ...

...

Bild 3.2 Beispiel zur Vererbung von Daten und Methoden

Im Programmcode kennzeichnet man die Vererbung in der Kopfzeile der abgeleiteten Klasse.

class Transportmittel { double breite, hoehe;

void bewegen() {...}

}

class Landtransportmittel extends Transportmittel { int radzahl;

void fahren() {...}

void schieben() {...}

}

class Auto extends Landtransportmittel { double spritverbrauch;

void starten() {...}

void fahren() {...}

void schieben() {...}

}

...

...

...

Im folgenden Programmausschnitt wird als Beispiel ein Objekt "auto" vom Typ "Auto"

vereinbart.

public static void main(String[] args) { Auto auto = new Auto();

...

}

Bild 3.3 veranschaulicht, dass "auto" je ein Objekt der Klassen "Landtransportmittel" und

"Transportmittel" enthält. In ein Objekt einer abgeleiteten Klasse ist also je ein anonymes Objekt seiner Oberklasse(n) eingebettet. Zu "auto" gehören folglich die Daten "breite",

"hoehe", "radzahl" und "spritverbrauch", sowie die Methoden "bewegen()", "fahren()",

"schieben()", "starten()", "fahren()" und "tanken()".

Transportmittel breite

hoehe bewegen() Landtransportmittel

radzahl fahren() schieben() Auto

spritverbrauch starten() fahren() tanken()

Bild 3.3 Ein Objekt der Klasse "Auto" enthält je ein Objekt der Klassen "Landtransportmittel" und

"Transportmittel"

Aufgabe 3.1

Warum lässt der Compiler die Zuweisung 1 zu, meldet dann aber für die Zuweisung 2 einen Fehler?

...

... (Programmcode zu Bild 3.3) ...

public class Test {

public static void main(String[] args) {

Landtransportmittel ltm = new Landtransportmittel();

Auto auto = new Auto();

ltm = auto; // Zuweisung 1 auto = ltm; // Zuweisung 2 }

}

Ein Beispiel

Ein Unternehmen hat unterschiedliche Mitarbeiter: Arbeiter, Vertreter, Manager, usw. Die Mitarbeiter unterscheiden sich z.B. in der Art, in der ihr Monatsgehalt berechnet wird.

• Arbeiter: Stundenlohn * Stunden,

• Vertreter: Stundenlohn * Stunden + Provision * Anzahl,

• Manager: Festes Gehalt,

• usw.

Für die Mitarbeiter sind Namen und gehaltsrelevante Daten zu speichern und die Namen und das zugehörige Gehalt sind auszugeben.

In einem ersten Programm wird für jeden Mitarbeitertyp eine eigene Klasse mit allen benötigten Daten vereinbart. Wir nutzen hier nicht das Konzept der Vererbung.

Beispiel, Employee1

class Worker //Arbeiter {

String name; // Name

double hourlyWage; // Stundenlohn double hours; // Stunden

Worker(String n, double hW, double h) { name = new String(n);

hourlyWage = hW;

hours = h;

}

void display() {

System.out.println(name + " "

+ hourlyWage * hours + " EUR");

} }

class SalesAgent //Vertreter {

String name; // Name

double hourlyWage; // Stundenlohn double hours; // Stunden double commission; // Provision double count; // Anzahl

SalesAgent(String n, double hW, double h, double c, double cn) {

name = new String(n);

hourlyWage = hW;

hours = h;

commission = c;

count = cn;

}

void display() {

System.out.println(name + " "

+ (hourlyWage * hours + commission * count)

+ " EUR");

} }

class Manager // Manager {

String name; // Name double salary; // Gehalt;

Manager(String n, double s) { name = new String(n);

salary = s;

}

void display() {

System.out.println(name + " "

+ salary + " EUR");

} }

public class Employee1 {

public static void main(String[] args) {

Worker w = new Worker("Meyer, Klaus", 15.82, 151.00);

SalesAgent s = new SalesAgent("Hamer, Peter", 8.80, 150.0, 60.28, 22.0);

Manager m = new Manager("Kramer, Hans", 3501.27);

w.display();

s.display();

m.display();

} }

Auf der Konsole

Meyer, Klaus 2388.82 EUR Hamer, Peter 2646.16 EUR Kramer, Hans 3501.27 EUR

Es liegt hier nah, zur Lösung eine Klassenhierarchie einzusetzen. Ein entscheidender Vorteil liegt darin, dass bestehender Code wieder verwendet werden kann. Bereits bestehende Konstruktoren und Methoden der Superklassen können wieder verwendet werden. Dort durchgeführte Sicherheitsprüfungen und Plausibilitätschecks werden übernommen, und so können robuste Programme geschrieben werden. Das Beispiel "Employee2" zeigt die Vorgehensweise. Es wir dieselbe Aufgabenstellung programmiert wie in Beispiel

"Employee1" aber diesmal unter Verwendung von Vererbung. Von der Basisklasse Employee wird die Klasse Worker abgeleitet und von dieser wiederum die Klasse SalesAgent. Von der Klasse Employee wird außerdem die Klasse Manager abgeleitet (Bild 3.4). Mit "super(..)"

kann in einer Klasse jeweils der Konstruktor der Oberklasse, der "Vatersklasse", aufgerufen werden. Dadurch ergibt sich in Klassenhierarchien eine Verkettung der Konstruktoren.

Methoden einer Superklasse, wie im nachfolgenden Beispiel die Methode display() der Klasse Worker, können in einer Subklasse überschrieben werden. Beim Überschreiben einer

Methode müssen aber die Signatur und der Rückgabewert der überschreibenden Methode identische sein mit der Signatur und dem Rückgabewert der überschriebenen Methode.

Employee

Worker Manager

SalesAgent Bild 3.4 Klassenhierarchie

Beispiel, Employee2 class Employee {

String name; // Name

Employee(String n) { name = new String(n);

}

void displayName() {

System.out.print(name + " " );

} }

class Worker extends Employee {

double hourlyWage; // Stundenlohn double hours; // Stunden

Worker(String n, double hW, double h) { super(n);

hourlyWage = hW;

hours = h;

}

double workerSalary() { return hourlyWage * hours;

}

void display() { displayName();

System.out.println(workerSalary() + " EUR");

} }

class SalesAgent extends Worker {

double commission; // Provision double count; // Anzahl

SalesAgent(String n, double hW, double h, double c, double cn) {

System.out.println((workerSalary() + commission*count) + " EUR");

} }

class Manager extends Employee {

public class Employee2 {

public static void main(String[] args) {

Worker w = new Worker("Meyer, Klaus", 15.82, 151.00);

SalesAgent s = new SalesAgent("Hamer, Peter", 8.80, 150.0, 60.28, 22.0);

Manager m = new Manager("Kramer, Hans", 3501.27);

w.display();

Meyer, Klaus 2388.82 EUR Hamer, Peter 2646.16 EUR Kramer, Hans 3501.27 EUR

Mit "super(..)" wird jeweils der Konstruktor der Oberklasse, des "Vatersklasse", aufgerufen.

Unmittelbare Zugriffe auf den Konstruktor der "Großvaterklasse" sind in Java nicht möglich In "SalesAgent" ist also z.B. kein Zugriff auf den Konstruktor von "Employee" durchführbar.

(Anweisungen wie "super.super(..)" sind nicht zulässig.)

Hinweis: Wenn ein Konstruktor die Anweisung "super(...)" enthält, muss sie die erste Anweisung dieses Konstruktors sein!

Aufgabe 3.2

Im Beispiel "Empoyee2" wird in der Klasse "Employee" die Methode "displayName()" in

"display()" umbenannt.

class Employee { String name;

Employee(String n) { name = new String(n); }

void display() { System.out.print(name + " " ); } }

Erläutern Sie dadurch verursachte Programmänderungen und die dadurch entstehenden Probleme.

Im nachfolgenden Programmbeispiel "Employee3" wird der Konstruktor der Oberklasse

"Employee" nicht mehr explizit mit "super(...)" aufgerufen. Die Konsolenausgabe zeigt, dass der Compiler dann selbständig einen Aufruf des Standardkonstruktors der Oberklasse generiert.

Beispiel, Employee3 class Employee {

String name;

Employee() {

System.out.println("--- Employee-Konstruktor ---" );

}

void displayName() {

System.out.print(name + " " );

} }

class Worker extends Employee {

double hourlyWage;

double hours;

Worker(String n, double hW, double h) { name = new String(n);

hourlyWage = hW;

hours = h;

}

double workerSalary() { return hourlyWage * hours;

}

void display() { displayName();

System.out.println(workerSalary() + " EUR");

} }

public class Employee3 {

public static void main(String[] args) {

Worker w = new Worker("Meyer, Klaus", 15.82, 151.00);

w.display();

} }

Auf der Konsole

--- Employee-Konstruktor --- Meyer, Klaus 2388.82 EUR

Hinweis: Wenn der Compiler im Konstruktor einer abgeleiteten Klasse als erste Anweisung kein "super(..)" findet, erzeugt er selbständig einen Aufruf des Standardkonstruktors der Oberklasse2.

Aufgabe 3.3

Im Beispiel "Empoyee3" wird in der Klasse "Employee" der Standardkonstruktor aus-kommentiert.

class Employee { ...

// Employee() { System.out.println("Employee-Konstruktor" ); } ...

Kommt es dann zu einer Fehlermeldung des Compilers?

Im nachfolgenden Beispiel werden noch einmal das Überschreiben von Methoden und das Verdecken von Membervariablen in einer Vererbungshierarchie dargestellt. Die überschriebene Instanzmethode methode() einer Vaterklasse kann mit super.methode() innerhalb von Instanzmethoden der abgeleiteten Sohnklasse angesprochen werden. Der Aufruf super.methode() wird dabei vom Compiler in ((Vaterklasse) this).methode() umgesetzt. Vom Verdecken einer Membervariable spricht man, wenn es in der Sohnklasse eine Membervariable mit gleichem Namen gibt, wie in der Vaterklasse. Der Zugriff auf eine von der Vaterklasse ererbte, verdeckte Instanzvariable x erfolgt mit super.x. Der Zugriff super.x wird dabei vom Compiler in den Zugriff ((Vaterklasse) this).x umgesetzt.

2 Es gibt eine Ausnahme zu dieser Regel, wenn ein Konstruktor einer Klasse einen anderen Konstruktor derselben Klasse aufruft ([2], [3]).

Beispiel, Ueberschreiben

//--- class A {

char c = 'A';

void f1() {System.out.println("f1 aus " + c);}

void f2() {System.out.println("f2 aus " + c);}

void f3() {System.out.println("f3 aus " + c);}

}

//--- class B extends A {

char c = 'B';

void f1() {System.out.println("f1 aus " + c +

" extends " + super.c);}

void f2() {System.out.println("f2 aus " + c +

" extends " + super.c);}

}

//--- class C extends B {

char c = 'C';

void f1() {

System.out.println("f1 aus " + c +

" extends " + super.c);

super.f1();

super.f2();

super.f3();

} }

//--- public class SuperDemo {

public static void main(String[] args) {

A a = new A();

a.f1();

a.f2();

a.f3();

B b = new B();

b.f1();

b.f2();

b.f3();

C c = new C();

c.f1();

c.f2();

c.f3();

} }

Auf der Konsole f1 aus A f2 aus A f3 aus A

f1 aus B extends A f2 aus B extends A f3 aus A

f1 aus C extends B f1 aus B extends A f2 aus B extends A f3 aus A

f2 aus B extends A f3 aus A

Im Dokument Einführung in Java (Seite 53-63)