Vererbung, Polymorphismus Einführung Beispiel Automobilbau: Eine Firma produziert eine Limousine Vererbung zur Erweiterung Vererbung zur Anpassung Oberklassen Aufruf Konstruktoren Polymorphismus Ein Kunde möchte einen solchen PW, aber als Kombi? Was macht die (gute) EntwicklungsingenieurIn? Mögliche Alternativen: Sie/er baut ein neues Auto von Grund auf Sie/er übernimmt möglichst viel von der bestehenden Limousine Am besten ist sie/er dran, wenn sie bereits das erste Auto so modular (mit Hilfe von Komponenten) entwickelt hat, dass die Umwandlung in einen Kombi einfach möglich ist 2 von 48 Lösung Ziele bei der Softwareentwicklung Beispiel Automobilbau: Autokonzern entwickelt eine Grundplattform Auf dieser Plattform können verschiedene Modelle verschiedener Marken aufgebaut werden Individualisierung geschieht über unterschiedliche Ausrüstung (=Erweiterung) Motorisierung (=Anpassung), etc. Vorteile: Hohe Entwicklungskosten können besser amortisiert werden Neue Modelle können schneller zur Marktreife gebracht werden Ziele bei der Softwareentwicklung: A) Software möglichst aus bestehenden Bausteinen (Klassen) zusammenbauen Steuerung B) Bei Bedarf diese Bausteine an die spezifischen Anforderungen anpassen selten passen die Bausteine zu 100% Anpassen von Software erweitern und modifizieren Lösung 1: Anpassen in der Quelle, oft möglich aber: Quelle kann nicht (mehr) vorhanden sein Symbol Symbol für für Aggregation Aggregation ("Bestandteil ("Bestandteil von") von") Ampel Änderungen sind schwierig, weil Quellen sehr umfangreich und Zusammenhänge u.u. nicht klar Änderungen (z.b. Bug Fix) müssen später u.u. an verschiedenen Quellen nachgeführt werden Änderungen können Einfluss auf bestehende Systeme haben 3 von 48 4 von 48
Objektorientierte Programmierung Die Objektorientiert Programmierung ermöglicht die Anpassung von Klassen, ohne dass man die Quellen benötigt Open-Closed Prinzip: Software-Einheiten sollten offen für Erweiterungen, aber geschlossen für Modifikationen sein Bestehende Bausteine dürfen nicht verändert werden Die Modifikation der Funktionalität sollte immer eine Erweiterung sein Bsp: z.b. eine Ampel unterstützt 3 statt 2 Phasen -> bestehende Steuerungen müssen nicht angepasst werden. Vererbung zur Erweiterung Der Mechanismus für die Erweiterung und Modifikation von Klassen heisst Vererbung 5 von 48 6 von 48 Vererbung zur Erweiterung Vererbung in Java Die Klasse Ballon ist gleich wie die Klasse, mit dem Unterschied, dass die Position nachträglich verändert werden kann. Die Klasse Ballon erweitert die Klasse (Klasse Ballon ist von der Klasse abgeleitet) Terminologie: Die Klasse heisst entsprechend Oberklasse (superclass) von der Klasse Ballon Die Klasse Ballon heisst Unterklasse (subclass) von der Klasse superclass, superclass, Oberklasse Oberklasse Basisklasse Basisklasse Symbol Symbol für für Vererbung Vererbung Es seien zwei Klassen gegeben: Klasse Klasse Ballon // gleich wie aber Position kann nachträglich verändert werden public class { private int x, y; public draw(graphics g) { g.filloval(..) public (int x, int y) {... public class Ballon { private int x, y; public draw(graphics g) { g.filloval(..) Code Code Verdoppelung Verdoppelung public Balloon(int x, int y) {... public void setpos(int newx, int newy){ x = newx; y = newy; Ballon subclass subclass Unterklasse Unterklasse abgeleitete Klasse abgeleitete Klasse 7 von 48 8 von 48
Vererbung in Java: extends Vererbung in Java Es seien zwei Klassen gegeben: Klasse (ev.vorbereitet für Vererbung) Klasse Ballon // gleich wie aber Position kann nachträglich verändert werden Ballon erbt von ; sämtliche protected: protected: sind sind in in abgeleiteten Ballon erbt von ; sämtliche abgeleiteten public und protected Methoden und Klassen Klassen sichtbar sichtbar (aber (aber nicht nicht in public und protected Methoden und in Attribute können innerhalb der andern andern Klassen) Attribute können innerhalb der Klassen) Klasse angesprochen werden Klasse angesprochen werden Klasse Ballon erbt Attribute (Instanzvariablen und Klassesnvariablen) und Operationen (Methoden) von der Klasse Attribute und Operationen der Klasse sind auch in der Klasse Ballon (wie die eigenen) zugreifbar. In Java kann nur von einer Klasse geerbt werden (Einfachvererbung ) im Gegensatz zu z.b. C++ (siehe Anhang) public class { protected int x, y; public class Ballon extends { public draw(graphics g) { g.filloval(..) public (int x, int y) { this.x = x; this.y = y; public void setpos(int newx, int newy){ x = newx; y = newy; public Ballon (int x, int y) { Ballon Methoden Methoden Ballon 9 von 48 10 von 48 Vererbung in Java Die Unterklasse erhält keine Kopien der Variablen und Methoden, sondern den Zugriff auf die Attribute und Methoden der Oberklasse Die Unterklasse kann sodann beliebig zusätzliche Methoden und Instanzvariablen deklarieren (wie bei jeder Klasse) Allgemeine Deklaration einer Unterklasse: Vererbung zur Anpassung class Unterklassenname extends Oberklassenname { Instanzvariablendeklarationen Konstruktorendeklarationen Methodendeklarationen 11 von 48 12 von 48
Vererbung zur Anpassung Überschreiben von Methoden Es seien zwei Klassen gegeben: Klasse Klasse Billard // gleich wie aber es wird noch eine Zahl angezeigt Falls eine Methode in der Unterklasse genau den gleichen Namen und die gleichen Parameter (Signatur) aufweist, wie eine Methode in der Oberklasse, so wird die Methode der Oberklasse überschrieben. Konkret: die Methode draw soll angepasst werden Wie die Funktionalität damit angepasst werden kann, wird im Abschnitt Polymorphismus genauer beschrieben Billardkugel Methoden Methoden Billardkugel Vorsicht: Falls die Deklaration nicht genau mit der in der Oberklasse übereinstimmt, wird die Methode überladen statt überschrieben! D.h. beide Methoden würden dann in der Unterklasse nebeneinander existieren, die geerbte und die neu geschriebene Überladen (overloading)!= Überschreiben (overriding) (siehe Anhang) 13 von 48 14 von 48 Vererbung in Java Klassendiagramm (class diagram) Es seien zwei Klassen gegeben: Klasse Klasse Billardkugel // gleich wie aber sie wird anders gezeichnet public class { protected int x, y; @Override @Override optional optional aber aber empfohlen empfohlen public void draw(graphics g) { g.filloval(..) public (int x, int y) { public class Billardkugel extends { @Override public void draw(graphics g) { g.filloval(..); g.drawstring (..); public Billardkugel (int x, int y) { Vererbungs-Hierarchie Eine Klasse ohne explizite Oberklasse erbt in Java implizit von der Klasse Object Jede Unterklasse kann seinerseits von weiteren Unterklassen erweitert werden Dadurch entsteht eine baumartige Hierarchie von Unterklassen (Baum steht aber auf dem Kopf) Diese Vererbungs-Hierarchie kann in einem sog. Klassendiagramm dargestellt werden Beispiel UML-Klassendiagramm: Vererbungs-Hierarchie der Klasse Ballon void setpos() Zeppelin Object equals() int x,y Billardkugel void move() 15 von 48 16 von 48
Die Klasse Object Überschreiben (shadowing( shadowing) ) von Attributen Jede Klasse in Java erbt automatisch und implizit von der Klasse java.lang.object Zwei Methoden somit in allen Klassen vorhanden (später mehr) Object String tostring() // Returns a string representation of the object. boolean equals(object obj) // Indicates whether some other object is "equal to" this one. Falls in der Unterklasse eine (public oder protected) Instanzvariable mit gleichem Namen wie in der Oberklasse deklariert wird, ist die Instanzvariable der Oberklasse in der Unterklasse nicht mehr zugreifbar. Dies wird auch als "Überschatten" (shadowing) bezeichnet public class { protected int x, y;... public class Billardkugel extends { private int x, y;... Führt oft zu Problemen Das Überschreiben von Attributen sollte vermieden werden! 17 von 48 18 von 48 Zugriffsmodifikator: protected Zugriffsmodifikatoren Übersicht protected-methoden/instanzvariablen sind sichtbar in allen Unterklassen der Klasse, in der die Variable/Methode deklariert ist Zugriffsmodifikator Sichtbarkeit gleiche Klasse Unterklasse andere Klasse Zur Erinnerung: lokale Variablen (innerhalb Methoden deklariert) sind nur innerhalb derselben Methode sichtbar private ja nein nein protected ja ja nein Billardkugel 19 von 48 public ja ja ja Grundsatz: Variablen immer mit den minimal möglichen/sinnvollen Sichtbarkeit deklarieren. Laufvariablen: for (int i = 0; lokale Variablen: void foo() {int i;... private Instanzvariablen: public class T {private int i; protected Instanzvariablen: public class T {protected int i; public Instanzvariablen: public class T {public int i;... 20 von 48
Aufruf von Methoden der Oberklasse Innerhalb einer Klasse kann jede beliebige Methode einer Oberklasse (wie eine eigene) aufgerufen werden. sofern sie public oder protected ist Oberklassen Methodenaufruf Beispiel: innerhalb der Klasse Zeppelin kann ich setpos() der Oberklasse (Ballon) aufrufen public void move (int dx) { setpos(x+dx, y); 21 von 48 22 von 48 Überschriebene Methoden Aufruf Eine Methode einer Oberklasse ist überschrieben, wenn in einer ihrer Unterklassen eine Methode mit demselben Methodenkopf (Signatur) steht Falls die ursprüngliche Methode in der Oberklasse von der Unterklasse aus aufgerufen werden soll, so muss der Objektname super verwendet werden: super.methodenname(...); super.super.methodenname ist nicht erlaubt! super.super.methodenname ist nicht erlaubt! Konstruktoren super bezeichnet das aktuelle Objekt, das aber als Objekt der Oberklasse interpretiert wird entsprechend wird die Methode der Oberklasse aufgerufen Beispiel: innerhalb der Klasse Billardkugel kann die draw Methode der Oberklasse aufgerufen werden public void draw (g) { super.draw(g); g.drawstring(..); 23 von 48 24 von 48
Konstruktoren Konstruktor ohne Parameter Repetition: Beim Erzeugen eines Objekts wird der entsprechende Konstruktor ausgeführt Instanzvariablen des erzeugten Objekts werden initialisiert kugel = new ( ); Jede Klasse hat einen oder mehrere Konstruktoren (heissen gleich wie Klasse) mit unterschiedlichen Parameterlisten Neu: Beim Erzeugen eines Objekts der Unterklasse, muss der entsprechende Konstruktor der Oberklasse ausgeführt werden, um dessen Instanzvariablen zu initialisieren public class { protected int x, y; public (int x, int y) { this.x = x; this.y = y;? public class Billardkugel extends { private int zahl; public Billardkugel (int x, int y, int zahl) {... Automatismus: Instanz- und Klassenvariablen werden automatisch d.h. von der Java Laufzeitumgebung zu 0 (null, false) initialisiert. Automatismus: Falls ein Konstruktor ohne Parameter (=Standardkonstruktor) in der Oberklasse vorhanden ist, dann wird dieser automatisch vor der Ausführung des Konstruktors der Unterklasse aufgerufen public class { protected int x, y; public () { this.x = 100; this.y = 100; automatisch, automatisch, implizit implizit public class Billardkugel extends { private int zahl; public Billardkugel (int x, int y, int zahl) { this.zahl = zahl; Initialisierung der Initialisierung der Unterklasse Unterklasse 25 von 48 26 von 48 Konstruktoren: super Konstruktoren Falls ein anderer als der Standardkonstruktor in der Oberklasse ausgeführt werden soll, so muss der Programmierer selbst diesen Oberklassen Konstruktur aufrufen. public class { protected int x, y; public (int x, int y;) { this.x = x; this.y = y; explizit explizit public class Billardkugel extends { private int zahl; public Billardkugel (int x, int y, int zahl) { super(x,y); this.zahl = zahl; Und zwar als erste Anweisung im Konstruktor der Unterklasse Automatismus: Falls eine Klasse keinen Konstruktor aufweist, wird von Java automatisch ein Standard-Konstruktor eingefügt. Wird aber wieder entfernt, sobald ein eigener Konstruktor vorhanden ist Dieser macht nichts anderes, als den Oberklassen Konstruktor aufzurufen und sieht dementsprechend folgendermassen aus: Klassenname(){ super(); Mit diesen Mechanismen garantiert Java, dass beim Erzeugen eines Objekts einer Klasse die Konstruktoren aller Oberklassen der Klasse ausgeführt werden, bevor der Konstruktor der Klasse selbst ausgeführt wird. Der erste Konstruktor, der immer ausgeführt wird ist also der Konstruktor der Oberklasse Object. Danach werden die Konstruktoren der direkten Unterklassen der Reihe nach ausgeführt bis zur aktuellen Klasse. 27 von 48 28 von 48
Konstruktoren Kontrollfrage Aufruf- Reihenfolge Ausführungs- Reihenfolge Object equals() Beispiel: Wird im Klassendiagramm ein Objekt der Klasse Zeppelin Was passiert, wenn die Unterklasse keinen Konstruktor hat und die Oberklasse hat einen, aber keinen ohne Aufrufparametern??!! int x,y erzeugt, so werden der Reihe nach folgende Konstruktoren ausgeführt: 1.Object(); 2.(); 3.Ballon(); Ballon Billardkugel 4.Zeppelin(); void setpos() Zeppelin void move() Die Konstruktoren werden von unten nach oben aufgerufen (explizit oder impliziert). Die Ausführungsreihenfolge ist dann aber von oben nach unten! 29 von 48 30 von 48 final bei Attributen und Methoden final bei Klassen Manchmal möchte man vermeiden, dass Instanzvariablen oder Methoden einer Klasse überschrieben werden können. Vorgehen: Variable oder Methode als final deklarieren final-methode oder -Variable kann nicht mehr überschrieben werden. public final static int DEFAULTX = 100; public final void setpos(int newx, int newy){ x = newx; y = newy; Es können auch ganze Klassen als final deklariert werden: Klasse kann nicht mehr erweitert (subclassed) werden Alle Methoden der Klasse sind dann automatisch auch final public final class Billardkugel extends { private int zahl;.. Erschwert/verhindert Erweiterbarkeit: sparsam einsetzen static final Variablen: entsprechen Konstanten in anderen Sprachen Klassenvariablen, die nicht überschrieben werden können. Java-Konvention: Namen von Konstanten in GROSSBUCHSTABEN schreiben Beispiel: Die Konstante Pi = 3.1415 ist in der Klasse Math definiert und kann von überall her mit dem Namen Math.PI verwendet werden Beispiel: Die Klasse System ist als final deklariert. 31 von 48 32 von 48
Erweitern von Java-Bibliotheksklassen Entwurfsgrundsatz: Vererbung Auch (alle nicht final) Klassen in Java-Bibliotheken können durch selbst programmierte Klassen erweitert werden (ohne den Java-Code kennen zu müssen!) Beispiel: Klasse Applet Um ein Applet zu schreiben, haben wir jeweils eine Unterklasse der Klasse Applet geschrieben: public MyProgram extends Applet {... MyProgram erbt alle Methoden von der Klasse Applet und deren Oberklassen Die Methoden add() und repaint() z.b. werden von den Oberklassen von Applet zur Verfügung gestellt Die Methoden init() und paint(graphics g) der Klasse Applet müssen in MyProgram überschrieben werden Wie findet man die nötigen Informationen über Bibliotheksklassen? In der Online-Dokumentation nachschauen In Java-Büchern (z.b. Java in a Nutshell ) Vererbung sollte nur verwendet werden, wenn eine "ist-ein"-beziehung zur Oberklasse besteht Bsp: Billarkugel ist eine Aber d.h. gesamte Funktionalität der macht auch bei der Billardkugel einen Sinn. Bsp: Billardkugel ist kein Ballon: Ballon kann Grösse verändern, steigen und sinken "Zeppelin ist Ballon" ist eigentlich schon ein Grenzfall 33 von 48 34 von 48 Übung Vererbungen in der Praxis Wiederverwendung von Software Entwickeln Sie aus: Fahrzeug, PKW, Taxi, Nutzfahrzeug, Bus, Bagger, Lastwagen, VW, Golf, Ferrari, Testerossa einen Vererbungsbaum. Zeichnen Sie das dazugehörige Klassendiagramm. spart Zeit (z.b. die Klassen von AWT und Klasse Math) Meist existiert schon Software, die einen Teil der Funktionalität aufweist Ändern einer bestehenden Software in der Quelle ist aufwendig: Quellen muss zuerst verstanden werden Neue Fehler, Inkompatibilitäten mit anderen Komponenten können entstehen Umfangreiche Tests auch der bestehenden Teile werden daher nötig Lösungsansatz der OOP: Die verwendeten Teile der Software werden belassen wie sie sind Nötige Änderungen und Ergänzungen erreicht man durch Vererbung und Erweiterung 35 von 48 36 von 48
Die Prinzipien der OOP Die zentralen Konzepte von OOP sind: Kapselung (encapsulation): Zusammengehörige Informationen in Objekten gruppieren und sie gegenüber der Aussenwelt abschirmen: letzte Vorlesung Polymorphismus Erweiterung durch Vererbung (inheritance): Neue Klassen können aus existierenden Klassen abgeleitet werden, ohne dass die Integrität der Originalklasse beeinträchtigt wird (Open-closed Prinzip): die vorhergehenden Folien dieser Vorlesung Polymorphismus (polymorphism): folgende Folien dieser Vorlesung 37 von 48 38 von 48 Casting up Casting down Erzeugen von Objekten und Zuweisen an Variable Figure fig = new Figure(.); Rectangle rect = new Rectangle(.); Upcast Fig fig = new Rectangle( ); In der Klassenhierarchie eine Referenz nach oben geben(casting up) Da jede erbende Klasse alle Methoden der Oberklassen besitzt ist das "sicher". Rectangle Line int x2,y2 Figure int x,y,w,h Circle int mx,my es kann nicht zu es kann nicht zu Fehlern führen Fehlern führen In der Klassenhierarchie eine Referenz nach unten geben (casting down, unsafe casting ) Fig fig = new Rectangle( ); in in fig fig könnte könnte auch auch Line, Line, Circle Circle oder oder Figure Figure sein sein Rectangle rect = fig; Rectangle Der Downcast ist "unsicher" und muss deshalb explizit angeben werden Rectangle rect = (Rectangle) fig; Figure int x,y,w,h Line Circle int x2,y2 int mx,my 39 von 48 40 von 48
ClassCastException, instanceof "Vielgestaltigkeit" Polymorphismus Folgende Sequenz führt zu einem Laufzeitfehler: ClassCastException Fig fig = new Circle( ); Rectangle rect = (Rectangle) fig; Polymorphismus ("Vielgestaltigkeit" ) beschreibt eine Eigenschaft objektorientierter Sprachen, bei der die korrekte Methode von mehreren gleichnamigen Methoden (im Bsp.: draw (Graphics g) ) durch den Objekttyp bestimmt wird, auf dem sie arbeiten soll. [Quelle: Java für Studenten] Vorher testen, ob ein Objekt zu einer bestimmten Klasse gehört oder nicht instanceof ist ein Schlüsselwort Verwendung if (fig instanceof Rectangle){Rectangle rect = (Rectangle) fig; class Circle extends Figure { public void draw(graphics g){ g.drawoval(x,y,w,h)... class Figure { protected int x,y,w,h; public void draw(graphics g){... class Rectangle extends Figure { public void draw(graphics g){ g.drawrect(x,y,w,h)... 41 von 48 42 von 48 Dynamischer, statischer Typ Prinzipien des Polymorphismus Welche draw() Methode wird aufgerufen? Fig fig = new Rectangle( ); fig.draw(g); Ein Objekt behält immer die Identität der Klasse, aus der es erzeugt wurde. (Ein Objekt kann nie in ein Objekt einer anderen Klasse konvertiert werden!) Figure int x,y,w,h Antwort: Es wird die draw Methode von Rectangle aufgerufen Regel: es wird immer die Methode der Klasse aufgerufen, die (dynamisch) der Variablen zugewiesen wurde. Wenn eine Methode auf ein Objekt aufgerufen wird, wird immer die Methode verwendet, die mit der Klasse des Objekt verbunden ist Rectangle Line int x2,y2 Circle int mx,my Statischer Typ: Typ der Variablen, wie sie deklariert wurde Dynamischer Typ: Typ (Klasse) des Objekts, das der Variablen zugewiesen wurde; Referenztyp -> auf den die Variable zeigt. 43 von 48 44 von 48
Beispiel einer Anwendung Zusammenfassung Ein kleines Zeichenprogramm (innerhalb einem Applet) Vererbung zur Erweiterung Vererbung zur Anpassung Oberklassen Aufruf Figure [] figs = new Figure[4]; void init () { figs[0] = new Circle(...); figs[1] = new Line(...); figs[2] = new Line(...); figs[3] = new Rectangle(...); Konstruktoren Polymorphismus void paint(graphics g) { for (int i = 0; i < figs.length; i++) { figs[i].draw(g)); 45 von 48 46 von 48 Anhang: overloading und overriding Anhang: Einfach und Mehrfachvererbung Overloading bedeutet, dass es zwei oder mehr Methoden mit dem gleichen Namen (innerhalb einer Klasse) gibt, die sich durch Parameter unterscheiden. Der Unterschied kann in der Anzahl der Parameter oder dem Typ oder beidem bestehen. Der Java-Compiler wählt aufgrund der Parameter die richtige aufzurufende Methode aus ("most specific" algorithm) Einfachvererbung (single inheritence): Eine Unterklasse kann in Java nur von einer Oberklasse erben, nicht von mehreren wie z.b. in C++ Problem: diamond inheritance Transportmittel Overriding (Überschreiben) einer Methode in einer Unterklasse bedeutet, dass eine Methode mit dem gleichen Namen und Parameter wie in der Oberklasse geschrieben wird. Das Java-Laufzeitsystem nimmt immer die Methode, die zur Klasse des Objekts gehört. [Quelle: Java für Studenten] Erbt die Klasse Wasserflugzeug die Methoden von Flugzeug oder von Boot? Für Mehrfachvererbung gibt es in Java zu diesem Zweck das Konzept der Interfaces: Nur Deklaration der Schnittstelle, keine Implementation der Klasse Problem wird umgangen! Flugzeug Wasser- Flugzeug << Boot 47 von 48 48 von 48