5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Übersicht 5.3 Subtypen und Vererbung Klassifikationen und Typisierung Typ beschreibt Eigenschaften von Objekten bzw. Werten. • Klassifikationen und Typisierung Annahme bisher: Kein Objekt bzw. Wert gehört zu mehr als einem (nicht-parametrisierten) Typ. • Schnittstellentypen in Java • Subtypbildung in Java Ansatz: • Dynamische Methodenauswahl • Realisiere jede Klasse/jeden Begriff einer Klassifikation im • Weitere Aspekte der Subtypbildung • Führe partielle Ordnung ≤ (vgl. F. 531) auf Typen ein, so dass - speziellere Typen gemäß der Ordnung kleiner als ihre allgemeineren Typen sind und - Objekte speziellerer Typen auch zu den allgemeineren gehören. Programm durch einen Typ. • Programmieren mit Schnittstellentypen ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1165 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung Klassifikationen und Typisierung (2) Beispiel: (Subtypbeziehungen) Wenn S ≤ T gilt, d.h. wenn S ein Subtyp von T ist, dann gehören alle Objekte von S auch zu T . In Java gibt es einen allgemeinsten Referenztyp, genannt Object. Wenn S ≤ T und S , T , heißt S ein echter Subtyp von T , in Zeichen S < T. Es gilt: String ≤ Object, MemoFrame ≤ Object, MemoFrame ≤ Frame, Wenn S ≤ T , dann heißt T ein Supertyp von S, und wir schreiben auch T ≥ S. int[] ≤ Object, Student ≤ Person, Person ≤ Object Wenn S < T und es kein U mit S < U < T gibt, dann heißt S ein direkter Subtyp von T . ©Arnd Poetzsch-Heffter TU Kaiserslautern 1166 1167 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1168 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Substituierbarkeit 5.3 Subtypen und Vererbung Beispiel: (Substituierbarkeit) Prinzip der Substituierbarkeit: Sei S ≤ T ; dann ist an allen Programmstellen, an denen ein Objekt vom Typ T zulässig ist, auch ein Objekt vom Typ S zulässig. Folgende Anweisungen sind typkorrektes Java: Object ov Konsequenzen: • Subtypobjekte müssen alle Eigenschaften des Supertyps aufweisen. = "Ein String ist auch ein Object "; Person [] p = new Person [4]; p[0] new Student (...); = • Eine Ausdruck von einem Subtyp kann an Stellen verwendet werden, an denen in Sprachen ohne Subtypen nur ein Ausdruck von einem allgemeineren Typ zulässig wäre. ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1169 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Bemerkung: 1170 5.3 Subtypen und Vererbung Referenztypen in Java In Java gibt es zwei zentrale Arten von benutzerdefinierten Typen: • Vereinfachend betrachtet, kann man Typen als die Menge ihrer Objekte bzw. Werte auffassen. • Klassentypen Bezeichne M(S) die Menge der Objekte vom Typ S. • Schnittstellentypen Für Typen S und T gilt dann: Darüber hinaus gibt es die zugehörigen Feldtypen und Aufzählungstypen. S ≤ T impliziert M(S) ⊆ M(T ) Eine Klasse deklariert einen Typ und beschreibt Objekte diesen Typs, d.h. u.a. deren öffentliche Schnittstelle und Implementierung. • In Java wird die Subtyprelation im Wesentlichen zusammen mit den Typdeklarationen definiert. ©Arnd Poetzsch-Heffter ©Arnd Poetzsch-Heffter TU Kaiserslautern 1171 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1172 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung Schnittstellentypen in Java Beispiel: (Schnittstellendeklaration) Eine Schnittstellendeklaration deklariert einen Typ T und beschreibt die öffentliche Schnittstelle, die alle Objekte von T haben. interface Person { String getName (); int getGeburtsdatum (); void drucken (); boolean hat_geburtstag ( int datum ); } Mögliche Implementierungen für Objekte von T liefern die echten Subtypen von T . Insbesondere lassen sich zu einem Schnittstellentyp T keine Objekte erzeugen, die nur zu T gehören. interface Druckbar { void drucken (); } Syntax der Schnittstellendeklaration: <Modifikatiorenlist > interface <Schnittstellenname > [ extends <Liste von Schnittstellennamen > ] { <Liste von Konstantendekl . und Methodensignaturen > } ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren interface Farbe { byte gelb = 0; byte gruen = 1; byte blau = 2; } 1173 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Subtypbildung in Java 1174 5.3 Subtypen und Vererbung Beispiel: (Subtyprelation bei Schnittstellen) 1. Die Typen Person, Druckbar und Farbe haben nur Object als Supertypen. Die Deklaration eines Typs T legt fest, welche direkten Supertypen T hat. 2. Der Typ Angestellte hat Person und Druckbar als direkte Supertypen: Bei einer Schnittstellendeklaration T gilt Folgendes: interface Angestellte extends Person , Druckbar { String getName (); int getGeburtsdatum (); int getEinstellungsdatum (); String getGehaltsklasse (); void drucken (); boolean hat_geburtstag ( int datum ); } • Gibt es keine extends-Klausel, ist Object der einzige Supertyp. • Andernfalls sind die in der extends-Klausel genannten Schnittstellentypen die direkten Supertypen. ©Arnd Poetzsch-Heffter ©Arnd Poetzsch-Heffter TU Kaiserslautern 1175 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1176 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Subtypbildung in Java (2) Subtypbildung in Java (3) Eine Schnittstellendeklaration erweitert also die Schnittstelle eines oder mehrerer anderer Typen. Syntax der Klassendeklaration: Methodensignaturen aus den Supertypen brauchen nicht nochmals aufgeführt werden (Signaturvererbung): <Modifikatiorenlist > class <Klassenname > [ extends <Klassenname > ] [ implements <Liste von Schnittstellennamen > ] { <Liste von Attribut -, Konstruktor -, Methodendekl .> } interface Angestellte extends Person , Druckbar { int getEinstellungsdatum (); String getGehaltsklasse (); } ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1177 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung 1178 5.3 Subtypen und Vererbung Beispiel: (Implementieren von Schnittstellen) Eine Klassen erweitert eine direkte Superklasse (s. 5.3.3) und implementiert ggf. mehrere Schnittstellentypen. • Gibt es keine extends-Klausel, ist Object die direkte Superklasse. Andernfalls ist die in der extends-Klausel genannte Klasse die direkte Superklasse. class Student implements Person , Druckbar { private String name; private int geburtsdatum ; // Form JJJJMMTT private int matrikelnr ; private int semester ; public Student ( String n,int g,int m,int s){ name = n; geburtsdatum = g; matrikelnr = m; semester = s; } public String getName () { return name; } public int getGeburtsdatum () { return geburtsdatum ; } • Die in der implements-Klausel genannten Schnittstellentypen müssen implementiert werden. Die Superklasse und alle implementierten Schnittstellentypen sind Supertypen der deklarierten Klasse. TU Kaiserslautern TU Kaiserslautern 5. Objektorientiertes Programmieren Subtypbildung in Java (4) ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung 1179 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1180 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Beispiel: (Implementieren von Schnittstellen) (2) } Zusammenfassung: Typen & Subtyp-Ordnung: public int getMatrikelnr () { return matrikelnr ; } public int getSemester () { return semester ;} public void drucken () { System .out. println ("Name:"+ name); System .out. println (" Gdatum :"+ geburtsdatum ); System .out. println (" Matnr :" + matrikelnr ); System .out. println (" Semzahl :"+ semester ); } public boolean hat_geburtstag ( int datum ) { return ( geburtsdatum %10000) ==(datum %10000) ; } ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung Typen: - elementare Datentypen: int, char, byte, .... - Schnittstellentypen - Klassentypen Referenztypen - Feldtypen - Aufzählungstypen 1181 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Zusammenfassung: Typen & Subtyp-Ordnung: (2) 1182 5.3 Subtypen und Vererbung Realisierung von Klassifikationen: Subtyp-Ordnung: Deklaration: impliziert interface S extends T1 , T2 , ... S <T1 , S <T2 , ... Die Klassen bzw. Begriffe in einer Klassifikation können im Programm durch Schnittstellen- oder Klassentypen realisiert werden. Deklaration: impliziert class S extends T implements T1 , T2 , ... S < T, S < T1 , S < T2 , ... Wir betrachten die Klassifikation bestehend aus: S < T impliziert: Person, Druckbar, Student, Angestellte, WissAngestellte und VerwAngestellte. S[] < T[] und davon die reflexive, transitive Hülle. ©Arnd Poetzsch-Heffter TU Kaiserslautern 1183 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1184 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Realisierung von Klassifikationen: (2) Realisierung von Klassifikationen: (3) 1. Variante: Dazu die entsprechenden Typdeklarationen: Nur die Blätter der Klassifikation (Student, WissAngestellte, VerwAngestellte) werden durch Klassen realisert, alle anderen durch Schnittstellen. ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung interface Person { ... } interface Druckbar { ... } interface Angestellte extends Person , Druckbar { ... } class Student implements Person , Druckbar {... } class WissAngestellte implements Angestellte { ... } class VerwAngestellte implements Angestellte { ... } 1185 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren 1186 5.3 Subtypen und Vererbung Realisierung von Klassifikationen: (4) Realisierung von Klassifikationen: (5) 2. Variante: Das Klassendiagramm zur 2. Variante: Außer des Typs Druckbar realisieren wir alle Typen durch Klassen: class Person { ... } interface Druckbar { ... } class Student extends Person implements Druckbar class Angestellte extends Person implements Druckbar class WissAngestellte extends Angestellte class VerwAngestellte extends Angestellte ©Arnd Poetzsch-Heffter TU Kaiserslautern { ... } { ... } { ... } { ... } 1187 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1188 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Diskussion: 5.3 Subtypen und Vererbung Dynamische Methodenauswahl Verwendung von Schnittstellen in Java: • nur wenig über den Typ bekannt Die Auswertung von Ausdrücken vom (statischen) Typ T kann Ergebnisse haben, die von einem Subtyp sind. • keine Festlegung von Implementierungsteilen • als Supertyp von Klassen mit mehreren Supertypen Damit stellt sich die Frage, wie Methodenaufrufe auszuwerten sind. Hier sind die charakteristischen Beispiele: Verwendung von Klassen in Java, wenn • Objekte von dem Typ erzeugt werden sollen; • Vererbung an Subtypen ermöglicht werden soll. ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1189 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung Beispiel: (Methodenaufruf) Begriffsklärung: (dynamische Methodenauswahl) Welche Methode soll ausgeführt werden: Die auszuführende Methode zu einem Methodenaufruf: <ZielAusdr >.< methodenName >( <AktParam1 >,...); 1. Auswahl zwischen Methode der Super- und Subklasse: Frame f = new MemoFrame (); ... f. setBackground ( Color .red ); wird wie folgt bestimmt: 1. Werte <ZielAusdr> aus; Ergebnis ist das Zielobjekt. 2. Auswahl zwischen Methode verschiedener Subklassen: 2. Werte die aktuellen Parameter <AktParam1>, ... aus. static void alle_drucken ( Druckbar [] df) { int i; for( i =0; i<df. length ; i++) { df[i]. drucken (); } } ©Arnd Poetzsch-Heffter TU Kaiserslautern 1190 3. Führe die Methode mit Namen <methodenName> des Zielobjekts mit den aktuellen Parametern aus. Dieses Verfahren nennt man dynamische Methodenauswahl oder dynamisches Binden (engl. dynamic method binding). 1191 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1192 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Bemerkung: 5.3 Subtypen und Vererbung Beispiel: (Erweiterbarkeit) Wir gehen von einem Programm aus mit der Methode: Die Unterstützung von Subtypen und dynamischer Methodenauswahl ist entscheidend für die verbesserte Wiederverwendbarkeit und Erweiterbarkeit, die durch Objektorientierung erreicht wird. Zusätzlich werden diese Aspekte auch durch Vererbung unterstützt. static void alle_drucken ( Druckbar [] df ) { int i; for( i =0; i<df. length ; i++) { df[i]. drucken (); } } Druckbar ist dabei Supertyp von Student, Angestellte, WissAngestellte und VerwAngestellte. ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1193 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung Beispiel: (Erweiterbarkeit) (2) Weitere Aspekte der Subtypbildung Das Programm soll erweitert werden, um auch Professoren und studentische Hilfskräfte behandeln zu können. Es reicht, zwei Klassen hinzuzufügen: Dieser Unterabschnitt behandelt detailliertere Aspekte zu class Professor implements Person , Druckbar 1194 • der Subtypordnung {... } • Typtest und Typkonvertierungen class StudHilfskraft extends Student { ... } • Polymorphie Eine Änderung des ursprünglichen Programms ist NICHT nötig! ©Arnd Poetzsch-Heffter TU Kaiserslautern 1195 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1196 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Aspekte der Subtypordnung 5.3 Subtypen und Vererbung Aspekte der Subtypordnung (2) Subtyprelation bei Feldern: Zyklenfreiheit: Jeder Feldtyp mit Komponenten vom Typ S ist ein Subtyp von Object: Die Subtyprelation darf keine Zyklen enthalten (sonst wäre sie keine Ordnung). S[] ≤ Object D.h. folgende Zuweisung ist zulässig: Folgendes Fragment ist also in Java nicht zulässig: Object ov = new String [3] ; Ist S ≤ T , dann ist S[] ≤ T []. D.h. folgende Zuweisung ist zulässig: interface C extends A { ... } interface B extends C { ... } interface A extends B { ... } ©Arnd Poetzsch-Heffter Person [] pv = new Student [3] ; Diese Festlegung der Subtypbeziehung zwischen Feldtypen ist in vielen Fällen praktisch. TU Kaiserslautern 5. Objektorientiertes Programmieren 1197 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Aspekte der Subtypordnung (3) 1198 5.3 Subtypen und Vererbung Subtypen und elementare Datentypen: Problem: Statische Typsicherheit ist nicht mehr gegeben: Zwischen den elementaren Datentypen und den Referenztypen gibt es keine Subtypbeziehung: String [] strfeld = new String [2]; Object [] objfeld = strfeld ; objfeld [0] = new Object (); // Laufzeitfehler // ArrayStoreException int strl = strfeld [0]. length (); int Object , int boolean , double int d.h. die folgenden Zuweisungen sind unzulässig: boolean bv = 9; int iv = 3.4; Speicherzustand nach der zweiten Zeile: Wie in Haskell gibt es auch in Java die Möglichkeit, Werte eines elementaren Datentyps in Werte eines anderen Datentyps zu konvertieren (siehe unten). ©Arnd Poetzsch-Heffter TU Kaiserslautern 1199 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1200 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Subtypen und elementare Datentypen: (2) 5.3 Subtypen und Vererbung Subtypen und elementare Datentypen: (3) Javas Wrapper-Klassen sind im Paket java.lang definiert. Der Zusammenhang zwischen elementaren Datentypen und Referenztypen wird in Java über sogenannte Wrapper -Klassen erzielt. Folgendes Diagramm zeigt die Subtypbeziehungen: Ein Wrapper-Objekt für den elementaren Datentyp D besitzt ein Attribut zur Speicherung von Werten des Typs D. Anwendung von Wrapper-Klassen: Integer ist die Wrapper-Klasse für den Typ int: Integer iv = new Integer (7); Object ov = iv; int n = iv. intValue () + 23 ; ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1201 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Boxing, Unboxing, Autoboxing 1202 5.3 Subtypen und Vererbung Beispiel: (Autoboxing) Das folgende Programmfragment mit Autoboxing Die Umwandlung von Werten der elementaren Datentypen in Objekte der Wrapper-Klassen nennt man Boxing, die umgekehrte Umwandlung Unboxing. List <Integer > ints = new ArrayList <Integer >(); ints.add (1); int i = ints.get (0); Wo nötig führt Java 6 Boxing und Unboxing automatisch durch (Autoboxing). ist eine Abkürzung von ©Arnd Poetzsch-Heffter TU Kaiserslautern List <Integer > ints = new ArrayList <Integer >(); ints.add(new Integer (1)); int i = ints.get (0). intValue (); 1203 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1204 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Bemerkungen: 5.3 Subtypen und Vererbung Beispiel: (Comparable) public interface Comparable { public int compareTo ( Object o); } 1. Autoboxing erlaubt es, Werte elementarer Datentypen einfacher zusammen mit parameterischen Typen zu benutzen; kostet aber Zeit und Speicher. public class Main { static boolean issorted ( Comparable [] cf ) { int i; if( cf.length <2 ) return true; for( i=0; i<cf.length -1; i++) { if( cf[i]. compareTo (cf[i+1]) > 0 ) { return false; } } return true; } 2. Vorsicht: Objekte, die den gleichen Wert eingepackt haben, müssen nicht gleich sein. ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1205 5.3 Subtypen und Vererbung TU Kaiserslautern 1206 5.3 Subtypen und Vererbung Typkonvertierung von elementaren Typen: Werte eines elementaren Datentyps lassen sich mittels sogenannter Typkonvertierungen (engl. Cast) in Werte anderer Datentypen konvertieren: public static void main( String [] args ) { boolean b; Character [] cfv = new Character [4]; cfv [0] = new Character (’\’’); cfv [1] = new Character (’Q’); cfv [2] = new Character (’a’); cfv [3] = new Character (’b’); b = issorted (cfv); System .out. println ("cfv sortiert : " + b ); } ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren Beispiel: (Comparable) (2) } ©Arnd Poetzsch-Heffter double // float // long // int // short // byte // 1207 ©Arnd Poetzsch-Heffter dv dv fv fv lv lv iv iv sv sv bv bv = 3333533335.3333333; == 3.3335333353333335 E9 = (float) dv; == 3.33353344 E9 = (long) fv; == 3333533440 L = (int) lv; == -961433856 = (short) iv; == -20736 = (byte) sv; == 0 TU Kaiserslautern 1208 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Typkonvertierung von elementaren Typen: (2) 5.3 Subtypen und Vererbung Typkonvertierung von Referenztypen: Typkonvertierungen von Datentypen mit kleinerem Wertebereich in solche mit größerem Wertebereich werden automatisch durchgeführt: 3.4 + 7 Bei Referenztypen prüft ein Cast, ob das geprüfte Objekt zu dem entsprechenden Typ gehört: • falls ja, wird die Ausführung fortgesetzt; • falls nein, wird eine ClassCastException ausgelöst. ist äquivalent zu: 3.4 + ( double ) 7 Bemerkung: Casts auf Referenztypen sollten soweit möglich vermieden werden. ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1209 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung Beispiel: (Konvertieren von Referenztypen) Typtest: Number nv Object ov Number nv1 Integer iwv Integer iwv1 Float fwv Comparable c String sv Java bietet außerdem den Operator instanceof zum Typtesten an: ©Arnd Poetzsch-Heffter = = = = = = = = new Integer (7); ( Object ) nv; // upcast ( Object ) nv; // nv; // ( Integer ) nv; // downcast (Float ) nv; // ( Comparable ) nv; // ( String ) nv; // TU Kaiserslautern 1210 Comparable c; if( nv instanceof Comparable ) { c = ( Comparable ) nv; } else { throw new ClassCastException (); } 1211 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1212 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung Polymorphie: Beispiel: (Subtyp-Polymorphie) In Kapitel 3 (Folie 418) hatten wir Polymorphie wie folgt erklärt: Subtyp-Polymorphie erlaubt es insbesondere, inhomogene Listen zu verwenden, d.h. Listen mit Element unterschiedlichen Typs: Ein Typsystem heißt polymorph, wenn es Werte bzw. Objekte gibt, die zu mehreren Typen gehören. LinkedList ls = new LinkedList (); ls. addLast (" letztes Element "); (( String ) ls. getLast ()). indexOf ("Elem"); // liefert 8 ls. addLast ( new Float (3.3) ); // kein Uebersetzungsfehler (( String ) ls. getLast ()). indexOf ("Elem"); // Laufzeitfehler Begriffsklärung: (Subtyp-Polymorphie) Die Form der Polymorphie in Typsystemen mit Subtypen heißt Subtyp-Polymorphie. ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1213 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Polymorphie im Vergleich: 1214 5.3 Subtypen und Vererbung Beispiel: (Parametrische Listen) Vergleich von Subtyp- und parametrischer Polymorphie: • Subtyp-Polymorphie: → ermöglicht inhomogene Datenstrukturen; → benötigt keine Instanzierung von Typparametern; → ist sehr flexibel in Kombination mit dynamischer Methodenauswahl. • parametrische Polymorphie: LinkedList <String > ls = new LinkedList <String >(); ls. addLast (" letztes Element "); ls. getLast ()). indexOf ("Elem"); // liefert 8 ls. addLast ( new Float (3.3) ); // Uebersetzungsfehler ! ls. getLast (). indexOf ("Elem"); → vermeidet Laufzeitprüfungen bei homogenen Datenstrukturen (effizienter); → bietet mehr statische Prüfbarkeit (keine Ausnahmen zur Laufzeit). ©Arnd Poetzsch-Heffter TU Kaiserslautern 1215 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1216 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Programmieren mit Schnittstellentypen 5.3 Subtypen und Vererbung Drei Implementierungen von Dictionary: Wir demonstrieren die Anwendung von Schnittstellentypen an zwei charakteristischen Beispielen: In 3.2.2 wurden natürliche Suchbäume zur Realisierung der abstrakten Datenstruktur DICTIONARY betrachtet. 1. Implementierungen einer abstrakten Datenstruktur mit unterschiedlichen Laufzeit- und Speicherplatzeigenschaften: Hier behandeln wir drei andere Suchverfahren: Der Anwender der Datenstruktur wählt die Eigenschaften bei der Erzeugung aus. I Ansonsten benutzt die Anwendung nur die Methoden der Schnittstelle. → Drei Implementierungen für Dictionary I • A. Binäre Suche in Feldern • B. Balancierte Suchbäume • C. Hashing/Streuspeicherung 2. Der Anwender eines Objekts kennt nur den Schnittstellentyp des Objekts, aber nicht dessen Implementierung: → Beobachtermuster ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1217 5.3 Subtypen und Vererbung 1218 5.3 Subtypen und Vererbung Drei Implementierungen von Dictionary: (3) Dabei basiert die Verwaltung von Datensätzen auf drei Grundoperationen: Ziel ist es, Datenstrukturen zu finden, bei denen der Aufwand für obige Operationen gering ist. • Einfügen eines Datensatzes in eine Menge von Datensätzen; Entsprechend der Signatur von put gehen wir im Folgenden davon aus, dass ein Datensatz aus einem Schlüssel und einer Referenz vom Typ Object besteht. • Suchen eines Datensatzes mit Schlüssel k; • Löschen eines Datensatzes mit Schlüssel k. In vereinfachter Anlehnung an java.util.Dictionary legen wir folgende Schnittstelle zugrunde: class DataSet { int key; Object data; DataSet (int k, Object d){ key=k; data=d; } } interface Dictionary { Object get( int key ); void put( int key , Object value ); void remove ( int key ); } TU Kaiserslautern TU Kaiserslautern 5. Objektorientiertes Programmieren Drei Implementierungen von Dictionary: (2) ©Arnd Poetzsch-Heffter ©Arnd Poetzsch-Heffter 1219 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1220 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Drei Implementierungen von Dictionary: (4) A. Binäre Suche in Feldern Wir betrachten drei Implementierungen des Schnittstellentyps Dictionary und präsentieren jeweils Lineare Datenstrukturen (Listen, Felder) mit einem Zugriff über den Komponentenindex erlauben das Auffinden eines Datensatzes durch binäre Suche. • die Datenstruktur (Hier betrachten wir eine Realisierung mit Feldern ähnlich wie in den Übungen.) • die drei grundlegenden Operationen • eine einfache Komplexitätsabschätzung. ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1221 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Datenstruktur: 1222 5.3 Subtypen und Vererbung Datenstruktur: (2) public class ArrayDictionary implements Dictionary { private DataSet [] elems; private int capacity ; private int size; public ArrayDictionary () { elems = new DataSet [8]; capacity = 8; size = 0; } private int searchIndex ( int key ) { ... } public Object get( int key ) { ... } public void remove ( int key ) { ... } public void put( int key , Object value ) {...} } Ein Dictionary wird repräsentiert durch ein Objekt mit: • einer Referenz auf das Feld mit den Datensätzen • der Größenangabe des Feldes (capacity) • der Anzahl der gespeicherten Datensätze (size) Die Operationen gewährleisten folgende Invariante: • Die Datensätze sind aufsteigend sortiert. • Die Schlüssel sind eindeutig. ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 1223 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1224 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Datenstruktur: (3) Heraussuchen: Zum Einfügen, Suchen und Löschen benötigt man den Index, an dem die Operation ausgeführt werden soll: private int searchIndex ( int key ) { /* liefert Index ix von Datensatz mit Schluessel k, wobei gilt: - k == key , wenn so ein Eintrag vorhanden - k ist naechst groessere Schluessel als key , zu dem Eintrag vorhanden size , sonst */ ... } ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren 1225 public Object get( int key ) { int ix = searchIndex ( key ); if( ix == size || elems[ix]. key != key ){ return null; } else { return elems[ix ]. data; } } ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung Löschen: Einfügen: public void remove ( int key ) { int ix = searchIndex ( key ); if( ix!=size && elems [ix ]. key == key ){ /* Datensatz loeschen */ for( int i = ix +1; i<size; i++ ) { elems [i -1] = elems [i]; } size --; } } public void put( int key , Object value ) { int ix = searchIndex ( key ); if( ix == size || elems[ix]. key > key ) { /* neuen Datensatz eintragen */ size ++; if( size > capacity ) { // neues Feld anlegen DataSet [] newElems = new DataSet [2* capacity ]; for( int i = 0; i<ix; i++ ) { newElems [i] = elems[i]; } for( int i = ix +1; i<size; i++ ) { newElems [i] = elems[i -1]; } elems = newElems ; capacity = 2* capacity ; Bemerkung: Bei den Operationen ist eine schnelle Suche wichtig. Deshalb konzentrieren sich die algorithmischen Untersuchungen auf diese Operation. ©Arnd Poetzsch-Heffter TU Kaiserslautern 1227 ©Arnd Poetzsch-Heffter 1226 TU Kaiserslautern 1228 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Einfügen: (2) 5.3 Subtypen und Vererbung Suchen Das Arbeiten mit sortierten Feldern ermöglicht binäre Suche: } } else { // Elemente im Feld verschieben for( int i = size -1; i<=ix +1; i-- ) { elems [i] = elems[i -1]; } } elems [ix] = new DataSet ( key , value ); } else { // elems[ix ]. key == key elems [ix ]. data = value ; } ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren • Durch Vergleich mit Schlüssel des Datensatzes in der Feldmitte kann bestimmt werden, ob der gesuchte Satz in der unteren oder oberen Hälfte des Feldes liegt. • Suche in der bestimmten Hälfte weiter. private int searchIndex ( int key ) { if( size==0 || elems[size -1]. key < key ){ return size; } else { int ug = 0; int og = size -1; /* key <= elems [og]. key */ 1229 5.3 Subtypen und Vererbung 1230 5.3 Subtypen und Vererbung Diskussion: while ( ug <=og -2 ) { int mid = ug + (og -ug)/2; if( key < elems[mid ]. key ) { og = mid; } else { ug = mid; } } if( elems [ug]. key < key ) { return og; } else { return ug; } ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren Suchen (2) } } ©Arnd Poetzsch-Heffter TU Kaiserslautern Binäres Suchen verursacht logarithmischen Aufwand: O(log N). Ebenso das Herausholen eines Eintrags aus dem ArrayDictionary. Einfügen und Löschen benötigen in der gezeigten Variante linearen Aufwand: O(N). Vorteile: • einfach und speichersparend zu realisieren • schnelles Heraussuchen von Einträgen Nachteile: • Einfügen und Löschen sind vergleichsweise langsam. 1231 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1232 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung B. Balancierte Suchbäume Beispiel: (Strukturerhaltung) In 3.2.2 haben wir natürliche binäre Suchbäume betrachtet. Sofern binäre Suchbäume hinreichend gut ausgeglichen (balanciert) sind, ist der Aufwand aller drei Grundoperationen logarithmisch. Einfügen von 10 unter Erhaltung von Fast-Vollständigkeit Ziel ist es, bei den modifizierenden Operationen den Baum wenn nötig wieder auszubalancieren. (Wir betrachten hier nur das ausbalancieren nach Einfüge-Operationen.) Durch Anforderungen bzgl. einer Verteilung der Blätter und Höhen in Unterbäumen kann man ein Degenerieren verhindern; Aspekte: • Vorteil: geringer Aufwand für Grundoperationen kann zugesichert werden. • Nachteil: Strukturinvariante muss erhalten werden. • Kosten der Strukturerhaltung? ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1233 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Beispiel: (Strukturerhaltung) (2) 1234 5.3 Subtypen und Vererbung Begriffsklärung: (AVL-Baum) Adelson-Velskij und Landis schlugen folgende Balancierungseigenschaft vor: Ein binärer Suchbaum heißt AVL-ausgeglichen, höhenbalanciert und ein AVL-Baum, wenn für jeden Knoten K gilt: Die Höhe des linken Unterbaums von K unterscheidet sich von der Höhe des rechten Unterbaums höchstens um eins. Wegen der Balancierungseigenschaft mussten alle Knoten vertauscht werden. ©Arnd Poetzsch-Heffter TU Kaiserslautern 1235 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1236 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Vorgehen zum Einsatz von AVL-Bäumen Gestaltsanalyse: Frage: Hat jeder AVL-Baum logarithmische Höhe? (Dies ist die Voraussetzung, alle Grundoperationen mit logarithmischen Aufwand realisieren zu können.) • Gestaltsanalyse von AVL-Bäumen • Rotationen auf Suchbäumen Lemma: • Datenstruktur für AVL-Bäume Für die Höhe eines AVL-Baums mit N Knoten gilt: • Heraussuchen h ≤ 2 ∗ log(N + 1) + 1 • Balancieren nach Einfügen Beweis: siehe Vorlesung • Diskussion ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren 1237 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Rotationen auf Suchbäumen: 1238 5.3 Subtypen und Vererbung Rotationen auf Suchbäumen: (2) Eine Rotation ist ein lokale Reorganisation eines Suchbaums, bei der die Suchbaumeigenschaft erhalten bleibt, die Höhen der Unterbäume aber ggf. verändert werden. Rotation nach rechts (nach links entsprechend): Es gilt: • alle Schlüssel aus A sind echt kleiner als X • alle Schlüssel aus B sind echt größer als X und echt kleiner als Y • alle Schlüssel aus C sind echt größer als Y • X <Y ©Arnd Poetzsch-Heffter TU Kaiserslautern 1239 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1240 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Rotationen auf Suchbäumen: (3) 5.3 Subtypen und Vererbung Datenstruktur für AVL-Bäume: Doppelrotation links-rechts (rechts-links entsprechend): Zur Realisierung von AVL-Bäumen gehen wir von der Implementierung der natürlichen Suchbäume aus: • Die Baumknoten bekommen ein zusätzliches Attribut bf (balance factor), in dem die Höhendifferenz von linkem und rechtem Unterbaum gespeichert wird. • Die Operationen zum Einfügen und Löschen müssen angepasst werden (ggf. wird dazu auch die Datenstruktur erweitert). Auch bei der Doppelrotation bleibt die Suchbaumeigenschaft erhalten. ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1241 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Datenstruktur für AVL-Bäume: (2) 1242 5.3 Subtypen und Vererbung Datenstruktur für AVL-Bäume: (3) Ein Dictionary wird repräsentiert durch ein Objekt, das eine Referenz auf einen Binärbaum enthält: • Die Baumknoten sind gekapselt und können von außen nur • Der leere Binärbaum wird durch die null-Referenz repräsentiert indirekt über die Grundoperationen manipuliert werden. (leeres Dictionary). • Jeder Knoten eines nichtleeren Binärbaums wird durch ein Objekt vom Typ AVLNode repräsentiert mit Instanzvariablen für: I den Schlüssel I die Daten I die Referenz auf das linke Kind I die Referenz auf das rechte Kind I die Höhendifferenz bf ©Arnd Poetzsch-Heffter Datenstruktur-Invarianten: • Schlüssel kommen nicht doppelt vor. • Die Binärbäume sind Suchbäume. TU Kaiserslautern • Die Höhendifferenz ist korrekt belegt. 1243 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1244 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Datenstruktur für AVL-Bäume: (4) Datenstruktur für AVL-Bäume: (5) class AVLTreeDictionary implements Dictionary { private AVLNode root; private static class AVLNode { private int key; private Object data; private AVLNode left , right ; private int bf; } private AVLNode ( int k, Object d ) { key = k; data = d; } ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren } 1245 public AVLTreeDictionary () { root = null; // leeres Dictionary } public Object get( int key ) {.. .} private AVLNode searchNode ( AVLNode current , int key) {...} public void put(int key , Object value) {...} private AVLNode insertNode ( AVLNode curr , int key , Object v ) {...} private AVLNode rotate ( AVLNode curr) {...} public void remove ( int key ) { throw RuntimeException ("not available "); } ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Heraussuchen: 1246 5.3 Subtypen und Vererbung Heraussuchen: (2) • Ist der Wurzelschlüssel gleich dem gesuchten Schlüssel, terminiert das Verfahren. • Ist der Wurzelschlüssel größer als der gesuchte Schlüssel, suche im linken Unterbaum weiter. • Ist der Wurzelschlüssel kleiner als der gesuchte Schlüssel, suche im rechten Unterbaum weiter. ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 1247 public Object get( int key ) { AVLNode tn = searchNode (root ,key); if( tn == null ) { return null; } else { return tn.data; } } ©Arnd Poetzsch-Heffter TU Kaiserslautern 1248 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Heraussuchen: (3) Einfügen: private AVLNode searchNode ( AVLNode current , int key) { if( current !=null && key != current .key ) { if( current .key > key ) { return searchNode ( current .left , key ); } else { // current .key < key return searchNode ( current .right , key ); } } return current ; } ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Entwicklung des Algorithmus in 4 Schritten: 1. Einfügen ohne Aktualisieren von bf und Rotation 2. Aktualisieren von bf, aber ohne Rotation 3. Aktualisieren von bf mit Aufruf der Rotation 4. Rotation mit Aktualisieren von bf an den rotierten Knoten 1249 ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Einfügen: (2) Beispiel: 1. Einfügen ohne Aktualisieren von bf und Rotation: Einfügen von 33: 1250 5.3 Subtypen und Vererbung • Neue Knoten werden immer als Blätter eingefügt. • Die Position des Blattes wird durch den Schlüssel des neuen Knotens festgelegt. • Beim Aufbau eines Baumes ergibt der erste Knoten die Wurzel. • Ein Knoten wird in den linken Unterbaum der Wurzel eingefügt, wenn sein Schlüssel kleiner ist als der Schlüssel der Wurzel; in den rechten, wenn er größer. Dieses Verfahren wird rekursiv fortgesetzt, bis die Einfügeposition bestimmt ist. ©Arnd Poetzsch-Heffter TU Kaiserslautern 1251 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1252 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung Beispiel: (2) Rekursives Suchen der Einfügestelle und Einfügen: class AVLTreeDictionary implements Dictionary { ... private AVLNode root; ... public void put( int key , Object value ){ if( root == null ) { root = new AVLNode (key , value); } else { AVLNode res = insertNode (root ,key , value); if( res!=null ) root = res; } } ... } void insertNode ( AVLNode curr , int key , Object v ) { // pre: curr != null // if( key < curr.key ) { if ( curr.left == null ) { curr.left = new AVLNode (key ,v); } else { insertNode ( curr.left , key , v); } ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1253 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Rekursives Suchen der Einfügestelle und Einfügen: (2) } ©Arnd Poetzsch-Heffter 1254 5.3 Subtypen und Vererbung 2. Aktualisieren von bf, aber ohne Rotation • Einfügen wie oben. • Aktualisieren von bf soweit nötig: Die Höhendifferenz bf kann sich nur bei Knoten ändern, die auf } else if( key > curr.key ) { if ( curr.right == null ) { curr. right = new AVLNode (key ,v); } else { insertNode ( curr.right , key , v); } } else { // key == curr.key curr.data = v; } dem Pfad von der Wurzel zum eingefügten Knoten liegen. Nur an diesen Knoten kann die AVL-Eigenschaft verletzt werden. • Bestimmen des kritischen Knotens KK; das ist der nächste Elternknoten zum eingefügten Knoten mit |bf | = 2. • Rotiere bei KK, Rotationstyp ergibt sich aus Pfad von eingefügtem Knoten zu KK. Wir werden zeigen, dass durch die Rotation der Unterbaum mit Wurzel KK die gleiche Höhe erhält, die er vor dem Einfügen hatte. Die Balancierungsfaktoren an Knoten oberhalb von KK brauchen also nicht aktualisiert zu werden. ©Arnd Poetzsch-Heffter TU Kaiserslautern 1255 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1256 5. Objektorientiertes Programmieren 5.3 Subtypen und Vererbung 5. Objektorientiertes Programmieren Einfügen mit Aktualisieren von bf: Einfügen mit Aktualisieren von bf: (2) private boolean insertNode ( AVLNode curr ,int key , Object v) { // pre: curr != null // ens: result ==true , wenn h(curr) > old(h(curr)) // result ==false , sonst if( key < curr.key ) { if( curr.left == null ) { curr.left = new AVLNode (key ,v); curr.bf ++; // |curr.bf| < 2 return (curr.bf >0); } else { if( insertNode (curr.left ,key ,v) ){ curr.bf ++; return (curr.bf >0); } else { return false ; } } ©Arnd Poetzsch-Heffter TU Kaiserslautern 5. Objektorientiertes Programmieren 1257 } } else if( key > curr.key ) { ... // symmetrisch auf rechter Seite } else { // key == curr.key curr.data = v; return false; } ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern 5. Objektorientiertes Programmieren Bemerkung: 1258 5.3 Subtypen und Vererbung 3. Einfügen mit Aktualisieren von bf und Rotation: • Die obige Fassung veranschaulicht die Vorgehensweise, auf dem "Rückweg" von einem rekursiven Abstieg Operationen auszuführen. Problem: Die Rotation macht es nötig, den Elternknoten des kritischen Knotens zu modifizieren. Idee: Statt true/false liefert die Einfüge-Operation: • Die obige Fassung wird so nicht benötigt, da bf nur bis zu • null, wenn sich die Höhe geändert hat. kritischen Knoten zu aktualisieren ist. Ist der kritische Knoten gefunden, wird rotiert und damit die Aktualisierungen oberhalb unnötig. ©Arnd Poetzsch-Heffter 5.3 Subtypen und Vererbung TU Kaiserslautern • Die Referenz auf den möglicherweise rotierten Unterbaum, wenn sich die Höhe nicht geändert hat; deshalb ist ggf. root zu modifizieren. 1259 ©Arnd Poetzsch-Heffter TU Kaiserslautern 1260