CS1005 Objektorientierte Programmierung Bachelor of Science (Informatik) Th Letschert Datentypen und Datenstrukturen Selbst definierte Datenstrukturen Innere Klassen Seite 1 © Th Letschert Typen: 1. Kasper 2. Prinzessin 3. Großmutter Datentypen und Datenstrukturen Datenstrukturen: realisieren die Typen, sie agieren hinter den Kulissen Seite 2 Th Letschert Datentyp / Datenstruktur Typ / Datentyp / abstrakter Datentyp Sicht von außen / Sicht des Anwenders (Daten-) Typ ist charakterisiert durch sein Verhalten (oft) durch Interface (Schnittstelle) beschreibbar kann immer durch Spezifikation beschrieben werden Datenstruktur innere Sicht / Sicht des Implementierers Struktur / Aufbau / Organisation von Daten Ich bin ein Stack ! 5 2 push pop top Seite 3 Datentyp / Datenstruktur Datentyp / Datenstruktur / Interface public interface Stack { int top(); void push(int x); void pop(); } public class ArrayStack implements Stack { private int a[]; private int count; } public void push(int x) { .... } public void pop() { .... } Seite 4 Datentyp / Datenstruktur Datenstrukturen und Kollektionstypen Datentyp-Sicht der Liste: Kollektionstypen: Kollektion mit einem bestimmten Datentypen die Kollektionen Verhalten (definierte Reihenfolge) (beliebiger) Elemente verwalten Beispiele: Liste, Menge, Abbildung, Stack, Warteschlangen, ... gibt mir Dein erstes Element Kollektionstypen erfordern die Datentyp Liste Organisation ihrer Elemente in Datenstrukturen Weitere Beispiele Datenstruktur Datenstruktur-Sicht der Liste: linear über Verweise verkette Speicherbereiche Feld Liste Baum ... umfasst Knoten und Verweise Seite 5 Datenstruktur Liste Th Letschert Datentyp / Datenstruktur Collection Framework: Datenstrukturen hinter der Fassade von Datentypen Datentyp Liste Datenstruktur Liste LinkedList Datentyp Liste Datenstruktur Feld Die Begriffe Datenstruktur und Datentyp werden (leider) nicht immer klar getrennt. Bei der Datenstruktur steht die Speicherorganisation im Vordergrund, beim Datentyp die Schnittstelle. Aber: Die Schnittstelle legt eine bestimmte Speicherorganisation nahe und die Speicherorganisation ermöglicht eine bestimmte Schnittstelle. Seite 6 ArrayList Th Letschert Datenstruktur Baum Seite 7 Th Letschert Datenstruktur Baum Bäume: zykelfreie Graphen – Knoten – Kanten (eventuell gerichtet zu den „Kindern“ / Nachfolgern) baumartige Datenstrukturen: – Knoten: Speicherbereiche – Kanten: Verweise Einsatz baumartiger Datenstrukturen – Implementierung von „schwach sortierten“ Datentypen – Darstellung hierarchischer Information (z.B. Syntaxbäume) – etc. Bäume: zykelfreie verbundene Graphen + 1 Seite 8 7 4 Syntaxbaum zu (1-4)+7 Th Letschert Datenstruktur Baum Binäre Bäume – Knoten haben maximal 2 Nachfolger binäre Suchbäume – Knoten enthalten Informationen – Informationen kommen nicht doppelt vor – linke Nachfolger enthalten kleine Info – rechte Nachfolger enthalten größere Info Einsatz binärer Suchbäume – schwache Sortierung für Datenstrukturen in denen gesucht werden muss; z.B. Verzeichnisse oder Menge (schon drin?) – Suche im Suchbaum schneller als in unsortierter Liste – Einfügen schneller als in einer sortierten Liste Seite 9 6 3 1 9 5 12 binärer Suchbaum Suche nach 5: - 5 < 6 => links - 5 > 3 => rechts - 5 = 5 => gefunden Th Letschert Datenstruktur Baum im Collections Framework Bäume im Collections- Framework – java.util.TreeSet<E> Set <<interface>> SortedSet <<interface>> Mengen-Implementierung die einen Baum als Datenstruktur verwendet und die Ordnung der Elemente (compareTo) bei der Speicherung berücksichtigt. Vorteil gegenüber HashSet: Elemente können sortiert durchlaufen werden. Nachteil gegenüber HashSet: Operationen sind etwas langsamer AbstractSet TreeSet HashSet Map – java.util.TreeMap<K,V> Abbildungs-Implementierung die einen Baum als Datenstruktur verwendet und die Ordnung der Schlüssel (compareTo) bei der Speicherung berücksichtigt. Vorteil gegenüber HashMap: Schlüssel können sortiert durchlaufen werden. Nachteil gegenüber HashMap: Operationen sind langsamer Seite 10 <<interface>> SortedMap <<interface>> AbstractMap TreeMap HashMap Th Letschert Datenstruktur Baum im Collections Framework Beispiel: Vorkommen von Worten in Dateien Wortvorkommen: Wort ~> ( DateiName ~> { (Zeile, Spalte) | Wort kommt in Zeile an Spalte vor } ) static TreeMap< String, TreeMap<String, TreeSet<LineColumn>>> wordOccurrance = new TreeMap<String, TreeMap<String, TreeSet<LineColumn>>>(); Der Typ der Variablen wird mit Klassen definiert (statt mit Interfaces, wie die allgemeine Regel empfiehlt), da der Algorithmus die besonderen Fähigkeiten der Baum-Implementierungen benötigt. Seite 11 Th Letschert Datenstruktur Baum im Collections Framework File f = null; List<File> fileList = new ArrayList<File>(); Wortvorkommen feststellen while (true) { f = chooseFile("Text-Datei waehlen"); if (f == null) { break; } fileList.add(f); } for (File file : fileList) { String fileName = file.getCanonicalPath(); Scanner scan = new Scanner(file); int lineNr = 0; while (scan.hasNextLine()) { String inputLine = scan.nextLine(); lineNr++; String[] words = inputLine.split("\\s"); int fromIndex = 0; for (String word : words) { if (!word.matches("([a-zA-Z])([a-zA-Z0-9-_]|&[a|o|u|A|O|U]uml;)*")) { continue; } TreeMap<String, TreeSet<LineColumn>> wordOcc = wordOccurrance.get(word); if (wordOcc == null) { wordOcc = new TreeMap<String, TreeSet<LineColumn>>(); wordOccurrance.put(word, wordOcc); } TreeSet<LineColumn> filePos = wordOcc.get(fileName); if (filePos == null) { filePos = new TreeSet<LineColumn>(); wordOcc.put(fileName, filePos); } int cloumnPos = inputLine.indexOf(word, fromIndex); filePos.add(new LineColumn(lineNr, cloumnPos)); fromIndex = cloumnPos; } } } Seite 12 Th Letschert Datenstruktur Baum im Collections Framework Wortvorkommen ausgeben for (String word : wordOccurrance.keySet()) { System.out.println("Word: " + word); TreeMap<String, TreeSet<LineColumn>> fileOcc = wordOccurrance.get(word); for (String fn : fileOcc.keySet()) { System.out.println("File: " + fn); for (LineColumn lc : fileOcc.get(fn)) { System.out.print(" " + lc + ","); } System.out.println(); } } Seite 13 Th Letschert Datenstruktur Baum im Collections Framework final class LineColumn implements Comparable<LineColumn> { private int line; private int column; Wortvorkommen speichern public LineColumn(int line, int column) { this.line = line; this.column = column; } public boolean equals(LineColumn lc) { return line == lc.line && column == lc.column; } @Override public boolean equals(Object o) { if (o instanceof LineColumn) { return equals((LineColumn) o); } else { return false; } } @Override public String toString() { return "Line: " + line + " Column: " + column; } @Override public int hashCode() { return line * column + line + column; } } @Override public int compareTo(LineColumn o) { return this.line != o.line ? this.line - o.line : this.column - o.column; } Seite 14 Th Letschert Selbst definierte Datenstrukturen Seite 15 Th Letschert Datentyp Menge implementiert als Baum Interface Menge – definiert Funktionalität Implementierung als Suchbaum: BaumMenge Menge <<interface>> enthaelt(int): boolean einfuege(int): void – realisiert die Funktionalität mit einem Suchbaum <<realize>> BaumMenge 0..2 Knoten 0..1 wert: int Eine Instanz von BaumMenge enthält maximal einen Knoten (der anker genannt wird) Jeder Knoten enthält maximal zwei andere Knoten. anker enthaelt(int): boolean einfuege(int): void Binärbaum-Implementierung einer Menge von int-Werten Seite 16 Th Letschert Datentyp Menge implementiert als Baum public interface Menge { public void einfuege(int x); public boolean enthaelt(int x); } Interface Menge Implementierung BaumMenge public class BaumMenge implements Menge { public boolean enthaelt(int x) { return enthaeltR(x, anker); } public void einfuege(int x) { if ( anker == null ) anker = new Knoten(x,null,null); else einfuegeR(x, anker); } private Knoten anker = null; private static class Knoten { int wert; Knoten links; Knoten rechts; Knoten(int e, Knoten links, Knoten rechts){ wert = e; this.links = links; this.rechts = rechts; } } } Seite 17 Methoden: Rekursion über die Baumstruktur. statische innere Klasse: Lokale Klassendefinition (statisch: weil ohne Bezug zu einem Objekt.) Knoten ist eine rein interne Hilfsklasse Th Letschert Datentyp Menge implementiert als Baum Interface Menge Implementierung BaumMenge private void einfuegeR(int x, Knoten k) { if ( x == k.wert ) return; // schon drin else if (x < k.wert ) { if ( k.links == null ) k.links = new Knoten(x,null,null); else einfuegeR(x, k.links); } else { if (x > k.wert ) if ( k.rechts == null ) k.rechts = new Knoten(x,null,null); else einfuegeR(x, k.rechts); } } } Methoden: Rekursion über die Baumstruktur. Seite 18 Th Letschert Iterierbare Menge java.lang interface Iterable<T> Iterierbare Klassen haben eine Methode iterator, die einen Iterator über einen Element-Typ T liefert. interface Iterable<T> ist Bestandteil der Klassenbibliothek Iterator<T> iterator(); public interface Menge extends Iterable<Integer> { public void einfuege(int x); public boolean enthaelt(int x); } Jede Implementierung von Menge ist eine iterierbare Klasse mit der Methode iterator, die einen Iterator über Integer liefert. 6 3 1 9 5 iterator 1, 3, 5, 6, 9, 12 12 Seite 19 Der Iterator könnte z.B. einen InfixDurchlauf der Knoten liefern. Infix: erst links, dann Mitte, dann rechts Th Letschert Iterierbare Menge / Programmrahmen public class BaumMenge implements Menge { ...... public Iterator<Integer> iterator() { return new BaumIterator(); } class BaumIterator implements Iterator<Integer> { public boolean hasNext() { ????? } public Integer next() { ????? } } } public void remove() { throw new UnsupportedOperationException(); } Seite 20 nicht statische innere Klasse: Lokale Klassendefinition mit Bezug zu einem Objekt. (Ein Iterator muss sich auf den Baum beziehen, den er durchlaufen soll.) remove wollen wir nicht implementieren! Th Letschert Iterierbare Menge public class BaumMenge implements Menge { .... private Knoten anker = null; .... public Iterator<Integer> iterator() { return new BaumIterator(); } class BaumIterator implements Iterator<Integer> { LinkedList<Knoten> l; BaumIterator() { l = new LinkedList<Knoten>(); infix(anker); } private void infix(Knoten k) { if ( k == null ) return; infix(k.links); l.add(k); infix(k.rechts); } public boolean hasNext() { return ! l.isEmpty(); } public Integer next() { Knoten k = l.getFirst(); l.removeFirst(); return k.wert; } public void remove() { throw new UnsupportedOperationException(); } } Der Iterator speichert intern eine Liste der Knoten. in nicht-statischen Klassen ist ein Bezug auf das umfassende Objekt möglich. Hier ist das umfassende Objekt eine Instanz von BaumMenge und der Bezug geht zur Objekt-Variable anker dieser Instanz. remove wird nicht unterstützt. } Seite 21 Th Letschert Iterierbare Menge durchlaufen BaumMenge b = new BaumMenge(); for (Integer i : b) System.out.println(i); Iterator<Integer> iter = b.iterator(); while ( iter.hasNext() ) { System.out.println(iter.next()); } ausführliche Form Kurzform Seite 22 Th Letschert 1 Innere Klassen Seite 23 Th Letschert Statische innere Klasse / geschachtelte Klasse Statische innere Klassen unterscheiden sich nur in der Sichtbarkeit / Programmorganisation von anderen „normalen“Klassen class A In Methoden der statischen inneren Klasse können (nur) statische Komponenten der äußeren Klasse verwendet werden A.x bezieht sich auf eine Komponente der äußeren Klasse kurz x wenn x eindeutig ist { static T x; static class I { T x = A.x; ... } ... } Seite 24 Th Letschert Nicht-statische innere Klasse Nicht-statische innere Klasse (kurz: innere Klasse): jedes Exemplar hat einen (impliziten) Bezug zum erzeugenden Objekt class BaumIterator implements Iterator<Integer> { LinkedList<Knoten> l; private BaumIterator() { l = new LinkedList<Knoten>(); infix(BaumMenge.this.anker); // oder kurz: infix(anker); } ... etc. ... } kein static ! BaumMenge.this: Zeiger auf den Erzeuger anker ist eindeutig, darum hier anker ~ BaumMenge.this.anker Seite 25 Th Letschert Nicht-statische innere Klasse In Methoden der inneren Klasse: this zeigt auf ein Objekt der inneren Klasse ( auf „mich selbst“) A.this zeigt auf ein Objekt der äußeren Klasse (auf „meinen Erzeuger“) nicht statische innere Klasse Exemplar ... ... I() der new äußeren ...new I() Klasse A ... A-Objekt class A { .... class I { ... } .... Exemplar der inneren Klasse I ... ... this ...this ... A.this ...A.this ... } Struktur der Klassendefinitionen I-Objekt Seite 26 Th Letschert Statische / Nicht-statische innere Klasse public class BaumMenge implements Menge { // ... public Iterator<Integer> iterator() { return new BaumIterator(this); } private static class BaumIterator implements Iterator<Integer> { Normalerweise kann ein Bezug zum erzeugenden Objekt auch explizit gesetzt werden! LinkedList<Knoten> l; private BaumIterator(BaumMenge m) { l = new LinkedList<Knoten>(); infix(m.anker); } } } // ... Seite 27 Th Letschert Anonyme innere Klasse Anonyme Klasse: interface INF { public TR m(TP x); ... } Klasse mit Definition und Instanzierung in einem Anonyme Klassen sind immer innere Klassen Instanzierungen sind immer innerhalb einer Methode und damit in einer anderen Klasse anonyme Klasse: Instanzierungs-Stelle = Definitionsstelle class C { ... void f() { INF y = new ... }; } = class C { class Y implements INF { public TR m(TP x) {...} ... } Anonyme Klassen sind immer nicht statische innere Klassen Entwurfsentscheidung Java INF () { public TR m(TP x){ ...} ... }; void f() { INF y = new Y(); } } Seite 28 Th Letschert Nicht-statische innere Klasse in Methoden (Nicht statische) Klassen können innerhalb von Methoden definiert werden sie können anonym sein, müssen aber nicht können nur auf Variablen der umfassenden Methode zugreifen, wenn diese final sind. Wichtigste (bekannteste) Anwendung: GUIs / ActionListener Seite 29 Th Letschert Nicht-statische innere Klasse in Methoden public class Outer { public interface Int2Int { int f(int x); } private int x; public Outer(int x) { this.x = x; } public Int2Int genF (final int y) { return new Int2Int() { public int f(int a) { return a+x+y; } }; } Methoden-Variable public static void main( String[] args){ Int2Int ff = (new Outer(1)).genF(2); } System.out.println(ff.f(3)); Ausgabe 6 } Seite 30 Th Letschert Nicht-statische innere Klasse in Methoden public interface Int2Int { int f(int x); } public final class Outer { private int x; public Outer(int x) { this.x = x; } public Int2Int genF(final int y) { } } class Inner implements Int2Int { public int f(int a) { return a + x + y; } } return new Inner(); public static void main(String[] args) { Int2Int ff = (new Outer(1)).genF(2); System.out.println(ff.f(3)); } Das Gleiche mit einer benannten inneren Klasse Seite 31 Th Letschert Anonyme innere Klasse Anonyme innere Klassen public class Outer { – haben keine eigene Konstruktordefinition – können Objektvariablen definieren private int x; public Outer(int x) { this.x = x; } public Int2Int genF (final int y) { return new Int2Int() { private int b; public int f(int a) { int temp = b; b = a; return x+y+temp; } }; } Ausgabe ? } public static void main( String[] args){ Int2Int ff = (new Outer(1)).genF(2); System.out.println(ff.f(3)); System.out.println(ff.f(3)); } Seite 32 Th Letschert Innere Klasse: Übersicht innere Klasse: Klasse die innerhalb einer anderen definiert wird statische innere / geschachtelte Klasse (static inner class / nested class) Innere / geschachtelte Klasse ist ohne Bezug auf ein Objekt Die Definition der inneren Klasse kann nur statische Komponenten der äußeren Klasse nutzen Statische innere Klassen haben einen Bezug zum statischen Anteil des definierenden Kontexts (der Klasse in der sie definiert sind: ÄussereKlasse.....) (nicht statische) innere Klasse (inner class) Innere Klasse mit Bezug zu einem Objekt der äußeren Klasse (seinem Erzeuger) Die Definition der inneren Klasse kann statische und nicht statische Komponenten der äußeren Klasse nutzen. Innere Klassen dürfen (im Gegensatz zu statischen inneren Klassen) keine statischen Komponenten definieren. (Statisch heißt ohne Bezug zu einem Objekt, Objekt-Bezug widerspricht dem Konzept einer nicht-statischen Klasse) Innere Klassen haben eine Bezug zum definierenden Kontext (dem Objekt das sie erzeugt: ÄussereKlasse.this.... ) Seite 33 Th Letschert Datenstrukturen / Java-Sprachelemente Datenstrukturen bestehen meist aus Knoten die über Referenzen miteinander verknüpft sind werden (nicht nur) zur Realisation eines Kollektions-Typs verwendet Kollektionen bieten Iteration über ihre Elemente Iteratoren benötigen einen Bezug zu ihrem Erzeuger: der Kollektion über die sie iterieren sollen. Java-Sprachelemente Kollektionen implementieren Iterable Iteratoren implementieren Iterator Knoten werden als innere Klassen definiert Iteratoren werden als innere Klassen definiert Kollektions-Typen implementieren die Iterable-Schnittstelle Varianten Klassendefinition in Klasse: statisch nicht statisch nicht statisch, anonym Seite 34 Th Letschert