Objektorientierte Programmierung OOP

KE4 Vererbung; Begriffe, Beispiele und Definitionen

KE4 Vererbung; Begriffe, Beispiele und Definitionen


Kartei Details

Karten 94
Sprache Deutsch
Kategorie Informatik
Stufe Universität
Erstellt / Aktualisiert 28.02.2013 / 18.05.2022
Weblink
https://card2brain.ch/box/objektorientierte_programmierung_oop6
Einbinden
<iframe src="https://card2brain.ch/box/objektorientierte_programmierung_oop6/embed" width="780" height="150" scrolling="no" frameborder="0"></iframe>

Begriff: Welche Bedeutung hat der Begriff Taxonomie?

Taxonomien kennen wir aus der Biologie: Eine Taxonomie Taxonomie ist dort eine biologische Systematik, die dazu dient, Lebewesen gemäß ihrer angenommenen und nachgewiesenen Verwandschaftsbeziehung zu klassifizieren. Dabei werden bestimmte Merkmale herangezogen.

Begriff: Beschreiben Sie den Begriff der Vererbung.

Bei der Klassenvererbung (engl. class inheritance) erbt eine UnterklasseU die Attribute und Methoden einschließlich ihrer Implementierung von der OberklasseO, von der U abgeleitet wurde. Mittels Vererbung erhalten wir die Möglichkeit, auszudrücken, dass eine Klasse (hier U) ähnlich einer anderen Klasse (hier O) ist, mit gewissen Unterschieden, die sie spezieller machen. Um diese Unterschiede auszudrücken, können der abgeleiteten Klasse neue Attribute und Methoden hinzugefügt werden, oder die Implementierungen der geerbten Methoden verändert werden. Durch die Vererbung wird zwischen Unter- und Oberklasse eine ist-ein-Beziehung erzeugt

Beispiel: Vererbung einer Klasse in Java.

Wollen wir nun in Java ausdrücken, dass die Klasse Pflanze eine Unterklasse der extends Klasse Artikel ist, so geschieht dies mit Hilfe des Schlüsselworts extends und dem Namen der Oberklasse. [JLS: § 8.1.4]

class Artikel {

   double preis;

   double lieferePreis() {

   return this.preis;

   }

   void legePreisFest(final double neuerPreis) {

      this.preis = neuerPreis;

    }

}

class Pflanze extends Artikel { }

Verständnis: Erläutern Sie die Beziehung der Unter- und Oberklassen. Subtyp und Typenverträglichkeit.

Jede Klasse definiert einen eigenen Objekttyp. Durch die Vererbung entstehen zwischen den Objekttypen Beziehungen. Ein durch eine Klasse U festgelegter Typ ist genau dann ein Subtyp einer durch die Klasse O festgelegten Typs, wenn U eine Unterklasse von O ist. Wir sagen auch, der Objekttyp U ist verträglich mit dem Objekttyp O. Der Typ des null-Verweises ist mit allen Objekttypen verträglich. [JLS: § 4.10.2]

Aufgabe: Wie kann verhindert werden, dass Syubtypen von Klassen gebildet werden.

Durch die folgenden Anweisungen, wird beispielhaft gezeigt, wie eine Klasse geschütz werden kann.

 

class Zubehoer extends Artikel { } final class DiverseDekoration extends Zubehoer { }

Begriff: Substitutionsprinzip

Vererbung bedeutet, dass jedes Objekt der Unterklasse auch eines der Oberklasse ist. Jede Pflanze ist somit auch ein Artikel. Ein Objekt der Unterklasse kann somit überall verwendet werden, wo ein Objekt der Oberklasse erwartet wird. Diesen Umstand nennen wir Substitutionsprinzip. Objekte der Unterklasse substituieren Substitutionsprinzip Objekte der Oberklasse. Dies ist immer möglich, da die Unterklasse alle Attribute und Methoden der Oberklasse erbt.

Bemerkung: Wir können Verweisvariablen vom Typ der Oberklasse ein Objekt der Unterklasse zuweisen.

Artikel a = new Pflanze();

Dabei muss beachtet werden, dass an a dennoch nur auf Attribute und Methoden der Klasse Artikel zugegriffen werden kann.

a.legePreisFest(15.0);

// ist möglich

a.legeLagertemperaturFest(10.0);

// ist nicht möglich, da die

// Klasse Artikel diese

// Methode nicht besitzt

Aufgabe: Wofür wird die Methode instanceof verwendet?

Manchmal ist es nötig zu prüfen, ob eine Variable auf ein Objekt einer Unterklasse verweist. Dies kann mit Hilfe des Operators instanceof geschehen. Der Ausdruck x instanceof Y liefert genau dann true, wenn x nicht der nullVerweis ist und wenn die Klasse des Objekts, auf das x verweist ein Subtyp von Y ist. [JLS: § 15.20.2]

Artikel a = new Artikel();

Artikel p = new Pflanze();

boolean aIstPflanze = a instanceof Pflanze; // liefert

false

boolean pIstPflanze = p instanceof Plfanze; // liefert true

Beispiel: explizite Typumwandlung

// erzeugt einen Übersetzungsfehler:

double temp1 = p.liefereLagertemperatur();

// liefert das gewünschte Ergebnis:

double temp2 = ((Pflanze) p).liefereLagertemperatur();

 

Die explizite Typumwandlung kann auch in Verbindung mit einer Zuweisung verwendet werden.   Artikel k = new Pflanze();   Pflanze m = (Pflanze) k;

Bemerkung: Übersetzungsfehler  

Sowohl bei einer expliziten Typumwandlung, als auch bei einem instanceof-Ausdruck kann ein Übersetzungsfehler auftreten, wenn es zwischen den Typen auf Grund der Typhierarchie auf keinen Fall eine Subtypbeziehung geben kann.

Beispiel: Übersetzungsfehler

class A { }

class B extends A { }

class C extends A { }

B b = new B();

boolean isInstance = b instanceof C; // liefert Übersetzungsfehler

C c = (C) b; // liefert Übersetzungsfehler

Die beiden Anweisungen liefern Übersetzungsfehler, da ein Exemplar vom Typ B auf Grund der Klassenhierarchie niemals ein Exemplar vom Typ C sein kann.

Aufgabe: Beschreiben Sie die implizite Typenumwandlung

Pflanze s = new Pflanze();

Artikel t;

t = s;

Definition: Statischer Typ einer Verweisvariablen  

Der statische Typ einer Verweisvariablen ist immer der Typ, der bei der Deklaration vereinbart wurde. Er kann sich nicht im Laufe eines Programms verändern.

Definition: Dynamischer Typ einer Verweisvariablen  

Unter dem dynamischen Typ einer Verweisvariablen verstehen wir den Typ des Objekts, auf das die Variable zu diesem Zeitpunkt verweist. Da eine Verweisvariable im Laufe eines Programms auf verschiedene Objekte verweisen kann, kann sich auch der dynamische Typ ändern. Der dynamische Typ ist immer ein Subtyp des statischen Typs.

Bemerkung: Auswirkungen von Typumwandlungen (Cast)  

Wenn ein Cast auf einen Verweis angewendet wird, so wird lediglich der statische Typ verändert. Der dynamische Typ bleibt unverändert. So ist zum Beispiel der statische Typ des Ausdrucks ((Artikel) new Pflanze()) der Objekttyp Artikel, der dynamische Typ hingegen Pflanze.

Definition: Überschreiben von Methoden  

Eine Unterklasse U kann eine geerbte Methode m() durch Definition einer neuen Methode m(), die die gleiche Signatur hat wie die geerbte Methode, überschreiben (engl. override). Der Ergebnistyp in der Unterklasse muss identisch oder verträglich mit dem in der Oberklasse deklarierten Ergebnistyp sein. [JLS: § 8.4.8.1, § 8.4.8.3] Wird an einem Objekt der Unterklasse eine überschrieben Methode aufgerufen, so wird in der Unterklasse implementierte Methode ausgeführt. Statische Methoden können nicht überschrieben werden. [JLS: § 8.4.8.2] Eine Methode kann nicht überschrieben werden, wenn sie als final vereinbart ist. [JLS: § 8.4.3.3]

Aufgabe: was bewirkt der Befehl "super"?

Das Schlüsselwort super ermöglicht den Zugriff auf überschriebene Methoden der Oberklasse. So können wir in jeder Methode der Unterklasse mit Hilfe von super.methodenName() auf die Implementierung der Oberklasse zugreifen [JLS: § 8.4.8.1, § 15.12.4.9]. Dadurch vermeiden wir in der Klasse Pflanze den doppelten Quelltext.

Aufgabe: Erklären Sie den Begriff "Verdecken"

class Ober {

    int x;

}

class Unter extends Ober {

   double x;

}

In diesem Fall verdeckt (engl. hide) das Attribut double x der Klasse Unter das Verdecken von Ober geerbte Attribut int x. [JLS: § 8.3.3.2] Will man nun explizit auf int x zugreifen, so ist dies mit Hilfe von super möglich. [JLS: § 15.11.2]

class Ober {

   int x;

}

class Unter extends Ober {

   double x;

   void test() {

      int a = super.x; // Zugriff auf int x aus Ober

      double b = x; // Zugriff auf double x aus Unter

   }

}

Bemerkung: Keine Aneinanderreihung des Schlüsselworts super  

Es ist nicht zulässig, das Schlüsselwort super wie in super.super.gebeInformationenAus() mehrfach aneinander zu reihen, um auf diese Weise über mehr als eine Stufe in der Klassenhierarchie hinweg auf die Implementierung einer Methode oder ein verdecktes Attribut zuzugreifen.

Begriff: Klasse Object

Alle Klassen von Java sind implizit oder explizit Unterklassen der vordefinierte Klasse Object. Wenn eine Klassendefinition keinen extends-Ausdruck enthält, fügt der Java-Übersetzer implizit den extends-Ausdruck für die Klasse Object hinzu. [JLS § 4.3.2]

Methode: equals() der Klasse Object

equals() ist eine Methode, die einen Wahrheitswert zurück liefert und überprüft, ob das Objekt, an das die equals()-Methode gerichtet ist, dem im Argument der Methode referenzierten Objekt gleich ist. In der Implementierung der Klasse Object liefert die Methode equals() genau dann true, wenn Objekt-Identität vorliegt. Diese Semantik ist identisch mit dem Vergleich zweier Objektverweise mit Hilfe des „==“-Operators. So wird, falls a und b Objekte der Klasse Object sind, a.equals(b) das gleiche Ergebnis liefern, wie a == b In der Praxis wird diese vordefinierte Bedeutung der equals()-Methode oft als zu einschränkend und wenig nützlich empfunden. Deswegen wird equals() in Unterklassen von Object häufig überschrieben, um Objekte z. B. auf die Gleichheit relevanter Attribute hin zu testen.

Methode: toString() der Klasse Object

toString() liefert eine Zeichenkettenrepräsentation des Objekts, also ein Objekt der Klasse String, die dann ausgegeben oder dauerhaft auf einem Speichermedium abgelegt werden kann. Diese Methode sollte in der Regel von anderen Klassen überschrieben werden, so dass nützliche Informationen über das Objekt in Form einer Zeichenkette zurückgeliefert werden. Eine weiterer Vorteil des Überschreibens der toString()-Methode ist deren indirekte Verwendung in den Methoden print() und println() der Klasse PrintStream, die bei jeder unserer Ausgaben der Form System.out.print() zum Einsatz kommen. Bekommt eine solche Methode ein Objekt übergeben, so wird automatisch das Ergebnis der toString()-Methode ausgegeben. Würde beispielsweise die toString()-Methode der Klasse X immer "XXX" zurückliefern, so würde die Anweisung System.out.println(new X()) die Zeichenkette "XXX" ausgeben, ohne dass explizit die toString()-Methode aufgerufen wird.

Methode: getClass der Klasse Object

getClass() gibt ein Objekt der vordefinierten Klasse Class zurück. Ein Class-Objekt enthält detaillierte Informationen über die Klasse, deren Exemplar das Objekt ist, das den Methodenaufruf getClass() empfangen hat.

Bemerkung Überschreiben von Methoden  

Bevor Sie in einer eigenen Klasse eine Methode wie z. B. equals() oder toString() überschreiben, sollten Sie in jedem Fall die Dokumentation der Methode studieren.

Beispiel: Erzeugung eines Konstrukters in einer Oberklasse

Nun ergänzen wir die Klasse Artikel um zwei Konstruktoren.

class Artikel {

    double preis;

    String name;

   int artikelnr;

   Artikel(final double preis, final int artikelnr) {

      this.legePreisFest(preis);

      this.legeArtikelnummerFest(artikelnr);

   }

   Artikel(final double preis, final int artikelnr, final String name) {

      this(preis, artikelnr);

      this.legeNameFest(name);

   }

   // Methoden

   // ...

}

 

Beispiel: Erzeugung eines Konstrukters in einer Unterklasse

class Pflanze extends Artikel {

    double lagertemperatur;

     Pflanze(final double preis, final int artikelnr, final double lagertemp) {

          super(preis, artikelnr);

          this.legeLagertemperaturFest(lagertemp);

     }

     Pflanze(final double preis, final int artikelnr, final String name, final double lagertemp) {

          super(preis, artikelnr, name);

          this.legeLagertemperaturFest(lagertemp);

     }

     // Methoden

     // ...

}

Bemerkung: Standardkonstruktor

Besitzt die direkte Oberklasse keinen Standardkonstruktor, so muss es sich bei der ersten Anweisung eines Konstruktor der Unterklasse entweder um einen this()- oder einen super()-Aufruf handeln. Es ist jedoch nur einer der beiden möglich. Besitzt die direkte Oberklasse einen Standardkonstruktor und die erste Anweisung im Konstruktor der Unterklasse ist kein this()- oder super()-Aufruf, so wird

implizit ein super()-Aufruf für den Standardkonstruktor eingefügt.

Bemerkung: Konstruktorausführung

Anschließend wird der aufgerufene Konstruktor von U mit dem folgenden Verfahren ausgeführt [JLS: § 12.5]:

• Ist die erste Anweisung ein Aufruf eines Konstruktors der gleichen Klasse unter Verwendung von this oder ein Aufruf eines Konstruktor der Oberklasse unter Verwendung von super, dann wird dieser wiederum nach diesem Verfahren ausgeführt.

• Ist kein expliziter Konstruktoraufruf vorhanden und es handelt sich bei der aktuellen Klasse nicht um Object, so wird der Standardkonstruktor der Oberklasse aufgerufen. Ist in diesem Fall in der Oberklasse kein Standardkonstruktor vorhanden, führt dies schon bei der Übersetzung zu einem Fehler.

• Initialisiere die Attribute dieser Klasse.

• Führe den (restlichen) Rumpf des Konstruktors aus.

Aufgabe: Wann spricht man von einer statischen Bindung?

Wird diese Bindung während der Übersetzung festgelegt, so nennt man dies statisches Binden (engl. static binding).

Aufgabe: Wann wird von einer dynamischen Bindung gesprochen?

Eine Bindung zur Laufzeit wird auch dynamische Bindung (engl. dynamic binding) genannt.

Aufgabe: Beschreiben Sie den Begriff Polymorhie

Grob gesagt bedeutet Polymorphie, dass eine an ein Objekt gesandte Nachricht der Form o.m(...) den Aufruf der Methode m() bewirkt, die in der Klasse des Objekts, auf das o verweist, oder einer der Oberklassen definiert ist.

Begriff: Wie werden die Methoden bestimmt, die zur Laufzeit ausgeführt werden?

Die Bestimmung der Methode, die zur Laufzeit ausgeführt wird, läuft in zwei Schritten ab [JLS: § 15.12]. Der erste findet bei der Übersetzung statt und bestimmt die Signatur s der Methode, die zur Laufzeit ausgeführt werden soll. Zur Laufzeit wird dann bei Exemplarmethoden diejenige Methode ausgeführt, die der dynamische Typ mit Signatur s geerbt hat. Diese kann identisch mit der Methode, die zur Übersetzungzeit gefunden wurde, sein, aber sich auch davon unterscheiden, wenn eine der Klassen zwischen dynamischem und statischem Typ diese Methode überschrieben hat. Es wird immer diejenige Methode mit der Signatur s ausgeführt, die am weitesten unten in der Hierarchie des dynamischen Typs zu finden ist.

Um die passende Methode zu finden, muss der statische Typ des Ausdrucks oder

der Variablen bestimmt werden, an dem die Methode aufgerufen wird. Während der Übersetzungszeit wird in der Vererbungshierarchie, vom statischen Typ aus aufsteigend, diejenige Methode m gesucht, die am besten passt, also die speziellste Signatur besitzt, und die so weit unten wie möglich in der Hierarchie steht. Die Suche kann beendet werden, sobald eine Methode gefunden wurde, deren Parametertypen mit den statischen Typen der Argumente übereinstimmen. Ansonsten wird die Suche bis zur Klasse Object fortgesetzt.

Aufgabe: Auf was ist bei Klassenmethoden wären der Bindung zu achten.

Sollte es sich bei m um eine Klassenmethode handeln, so wird diese gebunden und auch zur Laufzeit ausgeführt, unabhängig vom dynamischen Typ. Bei Klassenmethoden gibt es folglich nur die statische Bindung.

Bemerkung: Polymorphie

Ein Typ A ist spezieller als Typ B, wenn A ein Subtyp von B ist. Bei mehreren Parametern wird verglichen, welche Methode die meisten spezielleren Typen besitzt. Kann keine eindeutige Entscheidung getroffen werden, so wird ein Übersetzungsfehler verursacht.

Bemerkung:  

Ist eines der übergebenen Argumente null, so ist der statische Typ mit allen Klassen verträglich, im Zweifelsfalls wird jedoch der speziellste angenommen.

Bemerkung: Mögliche Fehler und Warnungen zur Übersetzungszeit  

Es können zur Übersetzungszeit unter anderem in den folgenden Fällen Fehler und Warnungen auftreten:

1. In der gesamten Hierarchie des statischen Typs wird keine passende Methode gefunden.

2. Der Rückgabetyp, der zur Übersetzungszeit bestimmt wird, passt nicht zur Verwendung des Aufrufs.

3. Bei der Methode m, die zur Übersetzungszeit gefunden wird, handelt es sich um eine Exemplarmethode, die jedoch an einem Typnamen aufgerufen wird. Denn eine Exemplarmethode muss immer an einem Objekt aufgerufen werden.

4. Wenn eine statische Methode an einem Verweis auf ein konkretes Objekt aufgerufen wird, so wird eine Warnung erzeugt.

5. Es kann nicht bestimmt werden, welche Methode auszuführen ist, weil mehrere Methoden gleich gut zu den statischen Typen der Argumente passen.

Bemerkung: Mögliche Fehler zur Laufzeit

Ein sehr häufiger Fehler, der zur Laufzeit auftritt, ist eine NullPointerException. Diese tritt bei der Ausführung einer Exemplarmethode auf, wenn der Verweis auf das Objekt, an dem die Methode aufgerufen wird, null ist.

 

Beispiel: Statische vs. dynamische Bindung  

Der nachfolgende Quelltext soll den Unterschied zwischen dynamischer Bindung bei Exemplarmethoden und der statischen Bindung bei (Exemplar-)Attributen verdeutlichen:

public class A {

    public int x = 8;

    public int getX() {

         return x;

     }

}

public class B extends A {

     public int x = 10;

     public int getX() {

          return x;

     }

     public void testBinding() {

          A a = new A();

          B b = new B();

          A ab = new B();

          System.out.print(a.x); // 8

          System.out.print(b.x); // 10

          System.out.print(ab.x); // 8

          System.out.print(a.getX()); // 8

          System.out.print(b.getX()); // 10

          System.out.print(ab.getX()); // 10

     }

}

Auch wenn x ein Klassenattribut wäre, würde dies nichts an den Ausgaben ändern.

Begriff: Paket

Mit Paketen bietet Java einen hilfreichen Mechanismus an, um inhaltlich zusammengehörige Klassen und Schnittstellen in einer benannten Einheit, eben einem Paket, zu gruppieren. Pakete sind nützlich, um große Sammlungen von Klassen und Schnittstellen zu organisieren und mögliche Namenskonflikte zu verhindern. Weiterhin dienen sie zu Strukturierung der Sichtbarkeit von Klassen.

Definition: Vereinbarung eines Pakets  

Ein Paket (engl. package) wird wie folgt eingeführt:

package paketName;

Diese Anweisung muss am Anfang einer Übersetzungseinheit (z. B. einer einzelnen Quelldatei) vor die Klassendeklaration gesetzt werden, die zum Paket packageName gehören soll. [JLS: § 7]