VL13-Collections und Generizitaet
Transcription
VL13-Collections und Generizitaet
Faulheit professionell: Fertige Datenbehälter Das Java-Collections-Framework Typsicherheit Generische Klassen Das Java Collections Framework Grundlegende Interfaces Das Interface List Das Interface List Interface List extends Collection{ void add(Object o); void add(Object o, int index); void addAll(Collection c); void clear(); boolean contains(Object o); void remove (Object o); Object remove(int index); int size(); Object[] toArray(); // ... und viele weitere Methoden } geeignet für alle listenartigen Datenbehälter java.util.Collection: einige Implementierungen Das Java-Collections-Framework: List-Implementierungen Collection AbstractCollection List Achtung, RuntimeExceptions beachten! Abstract List ArrayList LinkedList Vector Verwendung der Collections: LiFo import java.util.*; // die Collections class LifoVariabel implements LiFo{ private List liste = new ArrayList(); // oder Vector oder LinkedList public void add(Object obj) { liste.add(obj,0); // vorn einfügen } public Object remove() { return liste.remove(0); // vorn wegnehmen } } Verwendung der Collections: FiFo import java.util.*; // die Collections class LifoVariabel implements LiFo{ private List liste = new ArrayList(); // oder Vector oder LinkedList public void add(Object obj) { liste.add(obj, // hinten einf. liste.add(obj);liste.size()-1); // hinten einfügen (default) } public Object remove() { return liste.remove(0); // vorn wegnehmen } } Iteratoren • Implementierungs-unabhängiges Durchlaufen einer Collection • Iterator: ein intelligentes Objekt, das durch die Collection navigieren und an beliebiger Stelle Elemente lesen, entfernen oder einfügen kann • Merkt sich seine Position für den nächsten Aufruf • Mehrere Iteratoren pro Collection sind möglich • Wie Affen an einer Palme • Zu erzeugen durch die statische Funktion listIterator(), die einen "Affen" vom Typ ListIterator abliefert und an den Anfang der Collection setzt. ListIterator Sortiertes Eintragen mit dem ListIterator • liste ist eine abwärts sortierte Liste, in die das Element elem einzutragen ist. • Iterator-Funktionen: – next() "klettert" eine Stufe weiter und liefert den Eintrag – nextIndex() liefert den Folgeindex, ohne zu "klettern" public void eintragen(Liste liste, Element elem) { ListIterator affe = liste.listIterator(); while ((Element)(affe.next()).greater(elem)) { */ nur weiterlaufen */ } liste.add(elem, affe.nextIndex()-1); } • Ein zweiter "Affe" könnte parallel etwas anderes an der selben Liste machen.. Typsicherheit Antwort moderner Programmiersprachen auf die Fehlinterpretation von Daten 1. 2. Einer Variable dürfen nur Werte ihres Typs zugewiesen werden. Auf einen Wert sind nur die Operationen seines Typs anwendbar. Universelle Transporttypen (Object...) unterlaufen den ersten Aspekt, aber... 1. 2. Explizite Typumwandlung wird vom Java-Laufzeitsystem (nachträglich) abgesichert (ClassCastException). Operationssicherheit wird durch die Java-Syntax gewährleistet. ...Trotzdem wären typsichere Datenbehälter besser... Element-Typen • Für die Übung haben Sie einen String-Stack implementiert. – Nach demselben Muster lässt sich ein Fahrzeug-, int- oder ObjectStack programmieren. – Vererbung hilft nicht – man muss neu programmieren. – Die einzelnen Stacks sind typsicher – d.h. der Benutzer kann sich darauf verlassen, Elemente des richtigen Typs zu lesen. • Die Standard-Datenbehälter arbeiten mit dem allgemeinen Element-Typ Object. – Für beliebige Element-Typen verwendbar. – Eintragen ist unmittelbar möglich. – Man muss jedes entnommene Element "downcasten", ehe man damit arbeiten kann – Typsicherheit ist nicht gegeben (aber die Typverwendung ist abgesichert) Typ-Parametrisierung • Statt einen Stack für jeden Elementtyp neu zu programmieren • - kann man ihn mit dem Element-Typ parametrisieren: class Stack <Elementtyp> { ...} • Bei der Instanziierung wählt man den Elementtyp: Stack <String> texte = new Stack <String>(); Stack <Konto> konten = Diamond new Stack <Konto>(); Operator • In Java 8 auch: Stack <String> texte = new Stack <>(); • Stack <Elementtyp> ist eine generische Klasse: eine Klasse definiert mehrere Typen. Definition generischer Klassen • statt Object wird jetzt die Typvariable T verwendet: class StackArray <T> { private T[] stack = new T[size]; private int top = 0; public void add(T elem) throws OverflowExc { if (top == size) throw new OverflowExc(); stack[top] = elem; top++; } public T remove() throws UndeflowExc { if (top==0) throw new UndeflowExc(); top --; return stack[top]; } } • Instanziierung: new StackArray<String>(), new StackArray<Person>(), etc. Benutzung generischer Klassen • new StackArray<String>(): – die Typ-Variable T wird überall durch String ersetzt – es können nur String -Elemente eingetragen werden (typsicher) stack.add("Hallo"); // ok stack.add(new Person()); // Fehler – ausgelesene Elemente haben den Typ String (ohne "downcast") String s = stack.remove(0); // ok Präzisierung am Beispiel ArrayList • Ein Blick in die (Java 5-)Dokumentation zeigt, dass der Typ ArrayList als generische Klasse definiert ist: ArrayList<E> • ArrayList<E> heißt generische oder typ-parametrisierte Klasse. • E heißt formaler Typ-Parameter (oder auch Typ-Variable) und steht für einen beliebigen Typ (hier als Elementtyp verwendet) • E kann in der Schnittstelle von ArrayList<E> als Parameter- und Rückgabetyp vorkommen: void add(int index, E element); E remove(int index); Typkompatibilität generischer Klassen • generische Klassen sind typsicher: • ArrayList<String> liste = new ArrayList<Book>; // Typfehler! • ArrayList<Object> liste = new ArrayList<Book>; // Typfehler! • warum? – der Rückgabetyp von (z.B.) remove ist unterschiedlich, – ArrayList<String> hat eine andere Schnittstelle als ArrayList<Book> Rohe Typen • ArrayList heißt "roher Typ" von ArrayList<E> – ArrayList ist die gemeinsame Klasse aller ihrer parametrisierten Typen (wichtig für statische Variablen). – Der Typparameter E ist überall ersetzt durch Object – Der rohe Typ ist nicht identisch mit ArrayList<Object> • new ArrayList(): – Instanziierung des rohen Typs. – Die Typ-Variable E wird überall durch Object ersetzt – So haben wir den Typ bisher benutzt. • Generische Typen sind mit ihrem rohen Typ kompatibel – ArrayList list = new ArrayList<String>(); // erlaubt! – downcast der gelesenen Werte erforderlich: String s = (String)list.remove(10); Rohe und parametrisierte Typen nicht mischen! – ArrayList list = new ArrayList<String>(); // erlaubt! – Durch Mischen roher und parametrisierter Typen geht die Typsicherheit verloren. – list.add(new Auto()); // erlaubt, weil der rohe Typ // mit Object arbeitet! Rohe Typen mit allergrößter Vorsicht als universelle Transporttypen (für Objekte parametrisierter Typen) benutzen! Keine Operationen darauf anwenden, sondern Objekte vorher einer Variable geeigneten Typs zuweisen! Besser: Finger weg! Parametrisierte Interfaces • Interfaces können genauso parametrisiert sein wie Klassen: interface Container <T> { void add(T elem); T remove(); } interface LiFo<T> extends Container<T> {} interface FiFo<T> extends Container<T> {} class StackArray<T> implements LiFo<T> {...} StackArray<String> = new StackArray<String>(); Parametrisierung im Java-Collections-Framework • Die Klassen des Java-Collection-Framework befinden sich im Paket java.util • Alle Klassen, Interfaces und abstrakten Klassen des Java-Collection-Frameworks sind mit dem Elementtyp parametrisiert. • Bis Java 1.4 gab es nur die rohen Typen (Elementtyp Object). • Das Java-Collection-Interface definiert drei Hierarchien von Datenbehältern: List, Set, Map. • Wenn Sie sich mit List gut genug auskennen, erkunden Sie die anderen! Das Interface List http://docs.oracle.com/javase/8/docs/api/ Das Interface Collection ..ab jetzt schauen Sie bitte ins Collections Famework, ehe Sie sich selbst einen Datenbehälter zimmern!