4. Vererbung Grundlagen der Vererbung 4. Vererbung Wir wollen ein Verwaltungsprogramm für CDs und Videos entwickeln. Wir stellen uns dazu folgende Klassen vor: CD titel: kuenstler: titelanzahl: spielzeit: habIch: kommentar: setzeKommentar(String) gibKommentar() setzeVorhanden(boolean) gibVorhanden() ausgeben() String String int int boolean String void String void boolean void Video titel: regisseur: spielzeit: habIch: kommentar: setzeKommentar(String) gibKommentar() setzeVorhanden(boolean) gibVorhanden() ausgeben() Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 String String int boolean String void String void boolean void 98 4. Vererbung Grundlagen der Vererbung Wenn wir diese beiden Klassen realisieren, stellen wir fest, dass der Quelltext der Klassen weitgehend identisch ist. ☞ siehe Beispiele Nachteile dieses Ansatzes: • erhöhter Aufwand der Erstellung • bei der Wartung von dupliziertem Code sind Änderungen an mehreren Stellen notwendig • Wartung ist fehleranfällig • erhöhter Testaufwand • erhöhter Aufwand bei der Nutzung der Klassen Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 99 4. Vererbung Grundlagen der Vererbung Idee der Vererbung • Statt die beiden Klassen CD und Video unabhängig voneinander zu definieren, definieren wir zuerst eine Klasse, die die Gemeinsamkeiten von CD und Video zusammenfasst. Wir wollen diese Klasse Medium nennen. • Wir definieren dann anschließend, dass eine CD ein Medium ist und ebenso, dass ein Video ein Medium ist. • Schließlich ergänzen wir die Klassen für CD und Video ausschließlich um ihre spezifischen Eigenschaften. ☞ Prinzip der Generalisierung Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 100 4. Vererbung Grundlagen der Vererbung Medium −titel: String −spielzeit: int −habIch: boolean −kommentar: String +void setzeKommentar(String) +String gibKommentar() +void setzeVorhanden(boolean) +boolean gibVorhanden() +void ausgeben() CD −kuenstler: String −titelanzahl: int Video −regisseur: String +String gibRegisseur() +String gibKuenstler() +int gibTitelanzahl() Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 101 4. Vererbung Grundlagen der Vererbung Terminologie • Eine Superklasse (Oberklasse) ist eine Klasse, die von anderen Klassen erweitert wird. • Eine Subklasse (Unterklasse) ist eine Klasse, die eine andere Klasse erweitert. Man sagt auch, dass die Subklasse von der Superklasse erbt. • Vererbung bedeutet, dass die Subklasse alle Datenfelder und Methoden von der Superklasse übernimmt. • Es werden keine Konstruktoren vererbt! • Klassen, die über eine Vererbungsbeziehung miteinander verknüpft sind, bilden eine Vererbungshierarchie. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 102 4. Vererbung Grundlagen der Vererbung Beispiel: Eine Vererbungshierarchie für graphische Elemente. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 103 4. Vererbung Vererbung in Java Vererbung in Java Syntax: public class Unterklasse extends Oberklasse { ... } • Mit Hilfe von extends wird die Oberklasse zu einer Klasse angegeben. • Java unterstützt ausschließlich einfache Vererbung, d.h. wir können höchstens eine Oberklasse angeben. • Ohne Verwendung von extends ist implizit die Klasse java.lang.Object die Oberklasse. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 104 4. Vererbung Vererbung in Java Definition der Oberklasse Medium: Definition der Unterklassen: public class Medium { private String titel; private int spielzeit; private boolean habIch; private String kommentar; ... } public class CD extends Medium { private String kuenstler; private int titelanzahl; ... } public class Video extends Medium { private String regisseur; ... } ☞ In den Unterklassen werden zusätzliche Datenfelder und Methoden deklariert. ☞ Darüberhinaus verfügt ein Objekt der Unterklasse über alle Datenfelder und Methoden der Oberklasse. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 105 4. Vererbung Vererbung in Java Vererbung und Zugriffsrechte • Von einer Unterklasse aus kann man nicht auf die private deklarierten Datenfelder und Methoden der Oberklasse zugreifen. • Daher gibt es zusätzlich den Modifikator (Zugriffsrecht) protected. • Das Zugriffsrecht protected – erlaubt den Zugriff von Unterklassen aus, – erlaubt den Zugriff für Klassen des gleichen Pakets und – verbietet den Zugriff für alle anderen Klassen. • Der Modifikator protected kann nur auf Datenfelder, Konstruktoren und Methoden angewendet werden, nicht auf Klassen. • Datenfelder werden üblicherweise nicht als protected deklariert, da dies die Kapselung schwächen würde. Stattdessen definiert man Zugriffsmethoden die protected oder public sind. • Typischer Einsatz von protected: Bei Hilfsmethoden, die nach außen verborgen werden sollen, für Unterklassen aber hilfreich sein können. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 106 4. Vererbung Vererbung in Java • Der Einsatz von protected ist nur über Paketgrenzen hinweg sinnvoll. • protected wird deutlich seltener als public und private eingesetzt. • Bevor Sie protected verwenden, sollten Sie kritisch prüfen, ob private nicht auch ausreicht. • Man beachte: protected ist weniger restriktiv als die Verwendung keines Modifikators. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 107 4. Vererbung Vererbung in Java Vererbung und Initialisierung • Üblicherweise sorgt ein Konstruktor dafür, dass die Datenfelder eines Objekts nach der Erzeugung in einem vernüftigem Zustand sind. • Wie ist das nun, wenn die Datenfelder durch Vererbung über verschiedene Klassen verteilt sind? • Unter- und Oberklasse bieten Konstruktoren an. • Die Unterklasse kümmert sich in ihrem Konstruktor nur um die Datenfelder, die in der Unterklasse definiert sind. • Damit auch die Datenfelder der Oberklasse korrekt initialisiert werden, rufen wir den Konstruktor der Oberklasse auf. • Der Aufruf des Konstruktors der Oberklasse erfolgt mit dem Schlüsselwort super. • Dieser Aufruf muss stets die erste Anweisung in einem Konstruktor der Unterklasse sein! Ansonsten fügt der Compiler den Aufruf eines parameterlosen Konstruktors für die Oberklasse ein. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 108 4. Vererbung Vererbung in Java public class Medium { private private private private String titel; int spielzeit; boolean habIch; String kommentar; public Medium(String titel, int spielzeit) { this.title = titel; this.spielzeit = spielzeit; this.habIch = false; this.kommentar = ""; } ... } Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 109 4. Vererbung Vererbung in Java public class CD extends Medium { private String kuenstler; private int titelanzahl; public CD(String titel, String kuenstler, int stuecke, int spielzeit) { super(title,spielzeit); this.kuenstler = kuenstler; this.titelanahl = stuecke; } ... } ☞ Sie sollten den Konstruktor der Oberklasse auch dann explizit aufrufen, wenn der Compiler diesen Aufruf automatisch einfügen würde. Es ist guter Programmierstil. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 110 4. Vererbung Vererbung in Java Vorteile der Vererbung (bis hierher) • • • • Vermeidung von Quelltext-Duplizierung Wiederverwendung von Quelltext Einfachere Wartung Erweiterbarkeit Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 111 4. Vererbung Subtyping Subtyping und Ersetzbarkeit • Wir wissen: Klassen definieren Datentypen. • Wir haben gelernt: Klassen bilden eine Klassenhierarchie. • Analog zur Klassenhierarchie entsteht somit auch eine Typhierarchie mit Suptypen (Untertypen) und Supertypen (Obertypen). • Der Typ, der durch eine Subklasse definiert wird, ist ein Subtyp des Typs, der durch die Zugeordnete Superklasse definiert wird. Prinzip der Ersetzbarkeit: Objekte von Subtypen können an allen Stellen verwendet werden, an denen ein Supertyp erwartet wird. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 112 4. Vererbung Subtyping Beispiel: Die folgenden Zuweisungen sind zulässig: Medium medium = new Medium(...); Medium cd = new CD(...); Medium video = new Video(...); • Eine Variable kann Objekte halten, deren Typ entweder gleich dem deklarierten Typ der Variablen ist oder ein beliebiger Subtyp des deklarierten Typs ist. • Bei der Zuweisung wird eine implizite Typumwandlung (Cast) durchgeführt. Umgekehrt gilt dies nicht. Die folgenden Anweisungen sind nicht zulässig: CD cd = new Medium(...); Video video = new CD(...); Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 113 4. Vererbung Subtyping Dieses Prinzip der Ersetzbarkeit gilt natürlich auch bei einer Parameterübergabe. public class MedienDatenbank { ... public void einfuegen(Medium medium) { ... } } Wir können diese Methode sowohl für Videos als auch für CDs verwenden. MedienDatenbank db = new MedienDatenbank(...); Video video = new Video(...); CD cd = new CD(...); db.einfuegen(video); db.einfuegen(cd); Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 114 4. Vererbung Subtyping • Variablen für Objekte sind in Java polymorphe Variablen. • Der Ausdruck polymorph (vielgestaltig) bezieht sich auf die Tatsache, dass eine Variable Objekte verschiedener Typen referenzieren kann. • Polymorphie ist einer der Grundpfeiler der Objektorientierung. • Polymorphie tritt in objektorientierten Sprachen in unterschiedlichen Zusammenhängen auf. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 115 4. Vererbung Die Klasse Object Die Klasse Object • Alle Klassen ohne explizit deklarierte Superklasse haben die Klasse Object als Superklasse. • Object gehört zum Paket java.lang. • Object verfügt über einige vordefinierte Methoden. Diese stehen somit allen Objekten zur Verfügung. • Es kann sinnvoll sein, diese Methoden in Unterklassen zu überschreiben, d.h. mit einer anderen Implementierung zu versehen. • Beispiel: toString() • genaueres zu den Methoden: siehe API-Dokumentation Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 116 4. Vererbung Überschreiben von Methoden Überschreiben von Methoden Die Methode ausgeben() in der Klasse Medium können wir folgendermaßen definieren: public void ausgeben() { System.out.print(titel + " (" + spielzeit + "Min)"); if (habIch) System.out.println("*"); else System.out.println(); System.out.println(" " + kommentar); } Jetzt erzeugen wir eine Instanz der Klasse CD: CD cd = new CD("Beggar\’s Banquet", "Rolling Stones", 10, 41); Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 117 4. Vererbung Überschreiben von Methoden Mit weiteren Anweisungen können wir einen Kommentar angeben und das wir im Besitz der CD ist. Die Anweisung cd.ausgeben(); erzeugt dann die Ausgabe Beggar’s Banquet (41 Min)* Die letzte Platte der Ur-Stones, ein absolutes Meisterwerk! Problem: • Die Ausgabe ist unzureichend. • Es werden nur die allgemeinen Informationen angezeigt, die in der Klasse Medium definiert sind. • Dedizierte CD-Informationen (Künstler, Titelanzahl) fehlen in der Ausgabe. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 118 4. Vererbung Überschreiben von Methoden • Um eine vernüftige Ausgabe für CDs zu erhalten, müssen wir die Methode ausgeben() überschreiben. • Eine Subklasse kann die Implementierung einer Methode überschreiben. • Dazu deklariert die Subklasse eine Methode mit der gleichen Signatur wie in der Superklasse, implementiert diese jedoch mit einem anderen Rumpf. • Bei Aufrufen an Objekte der Unterklasse wird die überschreibende Methode ausgeführt. Ein erster Versuch: public class CD extends Medium { ... public void ausgeben() { System.out.println( " " + kuenstler + ", " + titelanzahl + " Titel"); } } Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 119 4. Vererbung Überschreiben von Methoden Die Anweisung cd.ausgeben(); liefert jetzt: Rolling Stones, 10 Titel • Jetzt geben wir nur die CD-spezifischen Information aus, es fehlen die allgemeinen Informationen. • Auf die Instanzvariablen von Medium haben wir aber innerhalb von CD keinen Zugriff. Sollte man diese protected deklarieren? • Nein, u.U. können wir dies auch gar nicht, da wir nicht den Quelltext von Medium besitzen. • Stattdessen nutzen wir super, um innerhalb der überschreibenden Methode, die Methode der Oberklasse aufzurufen. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 120 4. Vererbung Überschreiben von Methoden Ein zweiter Versuch: public class CD extends Medium { ... public void ausgeben() { System.out.print("CD: " + kuenstler + ": "); super.ausgeben(); System.out.println( " " + titelanzahl + " Titel"); } } Jetzt liefert die Anweisung cd.ausgeben();: CD: Rolling Stones: Beggar’s Banquet (41 Min)* Die letzte Platte der Ur-Stones, ein absolutes Meisterwerk! 10 Titel Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 121 4. Vererbung Überschreiben von Methoden • Ein super-Aufruf in einer Methode hat immer folgende Form: super.Methodenname( Parameterliste ); Der Methodenname muss also explizit genannt werden. • Im Gegensatz zu super-Aufrufen in Konstruktoren kann der super-Aufruf in einer Methode an jeder beliebigen Stelle der Methode erfolgen. • Es muss auch kein super-Aufruf erfolgen und wenn dieser nicht erfolgt, wird auch nicht automatisch ein solcher Aufruf generiert. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 122 4. Vererbung Überschreiben von Methoden Vererbung und Methodenausführung Medium medium = new CD("Beggar\’s Banquet", "Rolling Stones", 10, 41); ... medium.ausgeben(); • Welche Methode ausgeben() wird hier ausgeführt? Die der Klasse Medium, weil die Variable medium von diesem Typ ist? ☞ statischer Typ der Variablen medium Die der Klasse CD, weil das Objekt, auf dem ausgeben() aufgerufen wird, eine Instanz von CD ist? ☞ dynamischer Typ der Variablen medium ☞ Entscheidend für die Methodenauswahl ist der dynamische Typ einer Variablen. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 123 4. Vererbung Überschreiben von Methoden Methoden-Polymorphie: • Methodenaufrufe in Java sind polymorph. • Derselbe Methodenaufruf kann zu unterschiedlichen Zeitpunkten verschiedene Methoden aufrufen. • Welche Methode tatsächlich aufgerufen wird, ist abhängig vom dynamischen Typ der Variablen, mit der der Aufruf durchgeführt wird. Weiteres Beispiel: • Wir gehen davon aus, daß die Klassen CD und Video die Methode ausgeben() aus Medium überschreiben. • Dann wird für i==0 die Methode ausgeben() von CD aufgerufen und • für i==1 die Methode ausgeben() von Video. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 Medium m; Medium m1 = new CD(...); Medium m2 = new Video(...); for (int i=0 ; i<2 ; i++) { if (i==0) m = m1; else m = m2; m.ausgeben(); } 124 4. Vererbung Überschreiben von Methoden Wir wollen die Frage, welche Instanzmethode bei einem Methodenaufruf aufgerufen wird, wird noch etwas genauer betrachten. Methodensuche: • Beim Aufruf einer Instanzmethode findet eine sogenannte Methodensuche statt. • Ausgehend vom dynamischen Typ der Variablen wird in der Vererbungshierarchie nach einer Methode gesucht, die auf die Signatur des Aufrufs passt. • Wenn die Klasse des dynamischen Typs keine solche Methode aufweist, wird in der direkten Oberklasse gesucht, usw. • Die Suche endet spätestens in der Klasse Object. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 125 4. Vererbung Überschreiben von Methoden Die Methode toString() • Jedes Objekt hat eine Methode toString(). • Diese Methode ist in der Klasse Object definiert. • toString() wird implizit aufgerufen, wenn eine Objektreferenz als Parameter der Methode println() verwendet wird. MeineKlasse obj = new MeineKlasse(); System.out.println(obj); • Die Implementierung in Object liefert einen String der Art: Klassenname@Referenz • Hier bietet es sich an, in Unterklassen die Methode toString zu überschreiben. Peter Becker, Programiersprache Java — FH Bonn-Rhein-Sieg, SS 08 126