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!
