Programmieren I. Listen mit wahlfreiem Zugriff. Vorlesung 1. Handout S. 1. Martin Schultheiß. Hochschule Darmstadt Sommersemester 2011.

Ähnliche Dokumente
Programmieren II. Hierarchie von Collections. Vorlesung 3. Handout S. 1. Dr. Klaus Höppner. Hochschule Darmstadt Sommersemester 2010

equals und hashcode SortedSet NavigableSet Assoziative Container Programmieren II Dr. Klaus Höppner Hochschule Darmstadt Sommersemester / 32

JAVA KURS COLLECTION

Einstieg in die Informatik mit Java

java.util. Sebastian Draack Department Informations- und Elektrotechnik Department Informations- und Elektrotechnik JAVA Collection-API

Nützliche Utility-Klassen des JDK

Grundkonzepte java.util.list

Java Einführung Collections

Einführung in die Informatik: Programmierung und Software-Entwicklung, WS 11/12 1. Kapitel 11. Listen. Listen

Programmieren in Java

Java Generics & Collections

Java Schulung. Objektorientierte Programmierung in Java Teil V: Die Java Collection Klassen. Prof. Dr. Nikolaus Wulff

Teil V. Generics und Kollektionen in Java

Übungsblatt Programmierung und Software-Entwicklung Generizität, Interfaces, Listen, Sortieralgorithmen & JUnit

public interface Stack<E> { public void push(e e); public E pop();

Grundlagen der Informatik Generische Klassen

Vorlesung 09: Mengen. Peter Thiemann SS 2010

Informatik II, SS 2014

Datenstrukturen in Java

Suchen. lineare Suche, binäre Suche, divide and conquer, rekursive und iterative Algorithmen, geordnete Daten, Comparable

ALP II Dynamische Datenmengen

Grundlagen der Informatik. Prof. Dr. Stefan Enderle NTA Isny

Collections. Datamining und Sequenzanalyse. Marvin Meusel, Bertram Vogel

Vorlesung Programmieren

Collections des Typs Set. Informatik B - Objektorientierte Programmierung in Java. Vorlesung 09: Collections 3. Collections des Typs Set.

Vom objektorientierten Entwurf zur Implementierung in Java. 2. Dezember 2014

4.4.9 Das Java Collection Framework

Java I Vorlesung Collections

Vorlesung Programmieren

Schwerpunkte. Verkettete Listen. Verkettete Listen: 7. Verkettete Strukturen: Listen. Überblick und Grundprinzip. Vergleich: Arrays verkettete Listen

Programmieren 2 Übung Semesterwoche 2

Vorlesung Programmieren

Einführung in die Programmierung

4. Algorithmen und Datenstrukturen I Grundlagen der Programmierung 1 (Java)

Assoziative Container in C++ Christian Poulter

ALP II Dynamische Datenmengen Datenabstraktion (Teil 2)

Algorithmen und Datenstrukturen in Java Jiri Spale, Algorithmen und Datenstrukturen in Java 1

Schnittstellen implementieren am Beispiel Suchbaum

1 Abstrakte Klassen, finale Klassen und Interfaces

Innere Klassen. Innere Klassen. Page 1. Lernziele: innere Klassen, statische geschachtelte Klassen, anonyme Klassen.

1 Polymorphie (Vielgestaltigkeit)

Programmierkurs Java

Software Engineering Klassendiagramme Assoziationen

1. Aufgabe (6 Punkte): Java-Programmierung (Arrays)

Kapitel 3: Datentyp Liste

Schnittstellen, Stack und Queue

Java I Vorlesung 6 Referenz-Datentypen

Äußere Form ArrayList Vererbung API. Einführung in Java. Arne Hüffmeier. Michelle Liebers, Dennis Hoffmann. Tilman Lüttje, Jean Wiele

Inhaltsverzeichnis. Praktikum Algoritmen und Datenstrukturen WS2004/2005 Paul Litzbarski Stefan Nottorf. Druckmanager allgemein 2.

Wie entwerfe ich ein Programm?

Algorithmen und Datenstrukturen (ESE) Entwurf, Analyse und Umsetzung von Algorithmen (IEMS) WS 2014 / Vorlesung 10, Donnerstag 8.

Verkettete Listen. Implementierung von einfach verketteten Listen. Implementierung von doppelt verketteten Listen

Fortgeschrittene Programmiertechnik Klausur SS 2015 Angewandte Informatik Bachelor

Counting - Sort [ [ ] [ [ ] 1. SS 2008 Datenstrukturen und Algorithmen Sortieren in linearer Zeit

Die Klasse Collection (gb) implementiert eine Hash-Tabelle, deren Elemente aus einem Wert-Schlüssel-Paar

Programmierstil. Objektsammlungen. Konzepte. Zwischenspiel: Einige beliebte Fehler... Variablennamen Kommentare Layout Einrückung

7. Schnittstellen Grundlagen zu Schnittstellen. 7. Schnittstellen

Einführung in die Informatik: Programmierung und Software-Entwicklung, WS 16/17. Kapitel 14. Bäume. Bäume 1

Java Collection Framework

Advanced Programming in C

Es sei a 2 und b 2a 1. Definition Ein (a, b)-baum ist ein Baum mit folgenden Eigenschaften:

Das Typsystem von Scala. L. Piepmeyer: Funktionale Programmierung - Das Typsystem von Scala

Stack stack = new Stack(); stack.push ("Würstchen"); string s = (string) stack.pop(); Console.WriteLine (s);

6. Interfaces Grundlagen der Programmierung 1 (Java)

Einfach verkettete Liste

Programmieren in C. Rekursive Strukturen. Prof. Dr. Nikolaus Wulff

Vorkurs Informatik WiSe 16/17

Organisatorisches. drei Gruppen Gruppe 1: 10:10-11:40, Gruppe 2: 11:45-13:15 Gruppe 3: 13:20-14:50

C# - Einführung in die Programmiersprache Arrays, Enumeration und Collections. Leibniz Universität IT Services Anja Aue

Datenstrukturen und Abstrakte Datentypen

1 Hashing und die Klasse String

Generische Datenstrukturen

Einstieg in die Informatik mit Java

Info B VL 8: Abstrakte Klassen & Interfaces

Praxis der Programmierung

Allgemeine Informatik II SS :30-13:30 Uhr

Einführung in die STL

12) Generische Datenstrukturen

Operatoren für elementare Datentypen Bedingte Anweisungen Schleifen. Programmieren I. Martin Schultheiß. Hochschule Darmstadt Wintersemester 2010/2011

Vererbung und Traits

Aufgabenblatt 2 Suchen und Sortieren. Aufgabe 1: Vergleiche und Sortieren in Java

Programmieren I. Strategie zum Entwurf von Klassen. Vorlesung 5. Handout S. 1. Martin Schultheiß. Hochschule Darmstadt Wintersemester 2010/2011

C++ Teil 5. Sven Groß. 13. Mai Sven Groß (IGPM, RWTH Aachen) C++ Teil Mai / 18

Programmieren I. Dateien: Allgemeines. Vorlesung 10. Handout S. 1. Martin Schultheiß. Hochschule Darmstadt Wintersemester 2010/2011

Assoziation und Aggregation

Einführung in die Informatik: Programmierung und Software-Entwicklung, WS 11/12. Kapitel 13. Bäume. Bäume

Einstieg in die Informatik mit Java

Informatik 11 Kapitel 2 - Rekursive Datenstrukturen

Einstieg in die Informatik mit Java

Repetitorium Informatik (Java)

Bäume. Text. Prof. Dr. Margarita Esponda SS 2012 O4 O5 O6 O ALP2-Vorlesung, M. Esponda

Tutorium zur Vorlesung Programmieren

Einstieg in die Informatik mit Java

Software Engineering Klassendiagramme Einführung

Institut für Programmierung und Reaktive Systeme 26. April Programmieren II. 10. Übungsblatt

C- Kurs 09 Dynamische Datenstrukturen

Parallele und funktionale Programmierung Wintersemester 2015/ Übung Abgabe bis , 10:00 Uhr

Vorlesung Datenstrukturen

Java Vererbung. Inhalt

Einführung in die Programmierung 1

Transkript:

Programmieren I Martin Schultheiß Hochschule Darmstadt Sommersemester 2011 1 / 78 Listen Collections Iteratoren Sets SortedSet NavigableSet Maps 2 / 78 Listen mit wahlfreiem Zugriff In der letzten Vorlesung wurde eine einfache Stapelliste implementiert. In der Praxis sind allerdings Listen mit einem Zugriff auf die Elemente über einen Index viel relevanter. Für diese sollen dann folgende Operationen möglich sein: Lesen/Setzen eines Elements an einem bestimmten Index Einfügen eines neuen Elements am Ende Einfügen eines neuen Elements an einem bestimmten Index Löschen eines Elements an einem bestimmten Index 3 / 78 Handout S. 1

Ansätze für die Implementierung Für die Realisierung solch eines Datencontainers werden üblicherweise zwei Ansätze verfolgt: Realisierung als Array, der bei Bedarf durch einen größeren ersetzt wird. Hier liegen die Daten somit in einem zusammen hängenden Speicherbereich entsprechend ihrer Reihenfolge. Realisierung als doppelt verkettete Liste. Hier kennt jedes Listenelement seinen Vorgänger und Nachfolger, die Speicherorte der Daten können im Arbeitspeicher verstreut sein. 4 / 78 Ansatz 1: Array Beide Ansätze sollen nun im Folgenden kurz skizziert werden. Die Grundlage für die Array-basierte dynamische Liste wurde bereits mehrfach im Praktikum gelegt. Hier wurde jeweils ein Array mit einer bestimmten Maximalkapazität angelegt, der dann nach und nach gefüllt wurde, und hierbei jeweils eine Zählvariable hochgezählt wurde. Entscheidender, noch fehlender Punkt ist das Wachsen der Liste über die (bisherige) Kapazität hinaus. 5 / 78 Liste 1 (mit Array) p u b l i c c l a s s L i s t 1 <T> { p r i v a t e Object [ ] data ; p r i v a t e i n t s i z e = 0 ; p r i v a t e i n t max = 1 6 ; p u b l i c L i s t 1 ( ) { data = new Object [ max ] ; p u b l i c i n t g e t S i z e ( ) { r e t u r n s i z e ; 6 / 78 Handout S. 2

Liste 1 (Forts.) p u b l i c i n t getmax ( ) { r e t u r n max ; p u b l i c void add (T v a l u e ) { i f ( s i z e==max) { Object [ ] a l t = data ; max *= 2 ; data = new Object [ max ] ; f o r ( i n t index =0; index<s i z e ; index++) { data [ index ] = a l t [ index ] ; data [ s i z e ++] = v a l u e ; 7 / 78 Liste 1 (Forts.) p u b l i c T g e t ( i n t index ) { i f ( index <0 index>=s i z e ) throw new IndexOutOfBoundsException ( ) ; r e t u r n (T) data [ index ] ; p u b l i c void s e t ( i n t index, T v a l u e ) { i f ( index>=s i z e ) throw new ArrayIndexOutOfBoundsException ( ) ; data [ index ] = v a l u e ; 8 / 78 Analyse Besonders interessant zur Beurteilung der Implementierung ist die Betrachtung der Laufzeiten: Die Zugriffszeit auf ein Element (mit get oder set) ist wie der Indexoperator von Arrays unabhängig von der Größe des Containers, also konstant. Beim Anhängen eines Elements ist die Lage komplexer. Wenn noch freie Positionen vorhanden sind, ist der Aufwand zum Besetzen des neuen Elements konstant. Beim Vergrößern der Kapazität müssen die bisherigen Elemente umkopiert werden. Dieser Aufwand verdoppelt sich jeweils analog zur Verdopplung der Kapazität, allerdings verdoppelt sich auch die Schrittzahl, bis die nächste Kapazitätserhöhung notwendig wird. Im Mittel ist der Aufwand zum Anhängen daher ebenfalls konstant. 9 / 78 Handout S. 3

Ansatz 2: Verkettete Liste Der Ansatz der doppelt verketteten Liste erinnert an die Stapelliste aus der letzten Vorlesung. Auch die verkettete Liste besteht aus Listenelementen. Diese enthalten neben dem eigentlichen Speicherwert auch Referenzen auf das vorherige und nachfolgende Element. Diese Listenelemente werden innerhalb einer Klasse verwaltet, die sich das erste und letzte Listenelement merkt, und die Methoden zum lesenden und schreibenden Zugirff auf den Wert an einem bestimmten Index zur Verfügung stellt. 10 / 78 Schaubild: Verkettete Liste Objekt Vorgänger Wert Nachfolger Objekt Vorgänger Wert Nachfolger Objekt Vorgänger Wert Nachfolger Objekt Vorgänger Wert Nachfolger 0 Beginn Ende 0 11 / 78 Klasse Element c l a s s Element<T> { p r i v a t e T v a l u e ; p r i v a t e Element<T> next = n u l l ; p r i v a t e Element<T> prev = n u l l ; Element (T v a l u e ) { t h i s. v a l u e = v a l u e ; T getvalue ( ) { r e t u r n v a l u e ; void s e t V a l u e (T v a l u e ) { t h i s. v a l u e = v a l u e ; 12 / 78 Handout S. 4

Klasse Element (Forts.) Element<T> getnext ( ) { r e t u r n next ; void setnext ( Element<T> next ) { t h i s. next = next ; Element<T> getprev ( ) { r e t u r n prev ; void setprev ( Element<T> prev ) { t h i s. prev = prev ; 13 / 78 Klasse List2 (verkettete Liste) p u b l i c c l a s s L i s t 2 <T> { p r i v a t e i n t s i z e = 0 ; p r i v a t e Element<T> f i r s t = n u l l ; p r i v a t e Element<T> l a s t = n u l l ; p u b l i c i n t g e t S i z e ( ) { r e t u r n s i z e ; p u b l i c void add (T v a l u e ) { Element<T> neu = new Element<T>( value ) ; neu. s e t P r e v ( l a s t ) ; i f ( l a s t!= n u l l ) l a s t. setnext ( neu ) ; l a s t = neu ; i f ( f i r s t==n u l l ) f i r s t = l a s t ; s i z e ++; 14 / 78 Klasse List2 (Forts.) p r i v a t e Element<T> elementat ( i n t index ) { i f ( index <0 index>=s i z e ) throw new IndexOutOfBoundsException ( ) ; Element<T> r e s u l t = f i r s t ; w h i l e ( index - -!= 0) { r e s u l t = r e s u l t. getnext ( ) ; r e t u r n r e s u l t ; p u b l i c T g e t ( i n t index ) { r e t u r n elementat ( index ). getvalue ( ) ; p u b l i c void s e t ( i n t index, T v a l u e ) { elementat ( index ). s e t V a l u e ( v a l u e ) ; 15 / 78 Handout S. 5

Analyse Auch die doppelt verkettete Liste kann man bezüglich ihres Laufzeitverhaltens betrachten: Wenn man auf ein Element mit bestimmten Index zugreifen will, geht dies nicht direkt, sondern durch Abzählen startend beim ersten Element (bzw. bei einer besseren Implementation für Elemente in der zweiten Hälfte vom letzten Element aus). In diesem Fall ist nur die Zugriffszeit auf das erste und letzte Element konstant, der Aufwand zum Zugriff auf ein Element in der Mitte ist jedoch hoch und wächst linear mit der Größe der Liste. Das Anhängen eines Elements hinten ist unabhängig von der Containergröße und daher konstant. 16 / 78 Laufzeitverhalten beim Einfügen/Löschen In den beiden Implementationen wurden nur Methoden zum Zugriff auf die Elemente des Containers und das Anhängen neuer Werte realisiert. Aber auch ohne Implementation der Methoden zum Einfügen und Löschen an anderen Stellen kann man deren Laufzeitverhalten beurteilen. Beim Einfügen oder Löschen eines Wertes am Anfang müssen bei Ansatz 1 (Array) alle nachfolgenden Elemente verschoben werden. Der Aufwand hierfür wächst linear mit der Größe des Containers. Demgegenüber muss zum Einfügen eines Wertes vorne bei Ansatz 2 (verkettete Liste) eine neue Instanz von Element erzeugt/zersört und Referenzen (für Vorgänger, Nachfolger) gesetzt werden. Der Aufwand hierfür ist unabhängig von der Größe des Containers, also konstant. 17 / 78 Laufzeitverhalten (Forts.) Beim Einfügen oder Löschen eines Wertes in der Mitte müssen bei Ansatz 1 (Array) wiederum alle nachfolgenden Elemente verschoben werden. Der Aufwand hierfür wächst somit als ebenfalls linear mit der Größe des Containers. Bei Ansatz 2 (verkettete Liste) ist der Aufwand zum Finden des Elementes in der Mitte linear, der Aufwand zum Einfügen oder Löschen an dieser Stelle dann konstant. Bei großen Containern überwiegt der Aufwand zum Finden der Mitte, somit ist wächst der Aufwand also linear zur Größe des Containers. 18 / 78 Handout S. 6

Übersicht Operation Array-basiert verkettete Liste ArrayList LinkedList Zugriff...... am Anfang konstant konstant... in der Mitte konstant linear... am Ende konstant konstant Einfügen/Löschen...... am Anfang linear konstant... in der Mitte linear linear *... am Ende konstant konstant * Finden linear; eigentliches Einf./Löschen konstant 19 / 78 Collections In java. util existieren fertige Klassen für Datencontainer, die das Interface Collection implementieren, und daher auch oft Collections genannt werden. Hierbei existieren drei wesentliche Arten von Containern: sequenzielle Container bei denen die Werte innerhalb des Containers durch die Position identifiziert werden, Mengen bei denen es nur relevant ist, ob ein Objekt in der Menge enthalten ist oder nicht, und die Reihenfolge irrelevant ist, assoziative Container bei denen die Werte mit einem Schlüssel assoziiert werden, analog zu einem Wörter- oder Telefonbuch. Diese implementieren das Interface Map statt Collection, werden aber trotzdem häufig zu den Collections gezählt. 20 / 78 Das Interface List Für sequenzielle Container mit einem wahlfreien Zugriff auf die Objekt-Positionen existiert das Interface List, das von zwei Klassen implementiert wird: ArrayList als Datencontainer, der intern einen Array verwaltet, analog zu Ansatz 1 aus dem letzten Abschnitt, LinkedList als doppelt verkettete Liste, analog zu Ansatz 2 aus dem letzten Abschnitt. Beide liegen als parametrisierte Klassen vor, können also auch direkt für einen bestimmten Typ neben der (alten) Roh-Klasse verwendet werden. 21 / 78 Handout S. 7

Methoden von List<E> boolean add(e e) fügt ein Objekt e hinten an. void add(int index, E e) fügt das Objekt e an Position index ein. void clear() löscht alle Werte aus der Liste. boolean contains(object o) gibt true zurück, wenn es in der Liste ein Element e gibt, für das der Ausdruck e.equals(o) wahr ist, sonst false. Es wird also nicht auf Identität der Referenzen, sondern Gleichheit (Äquivalenz) der Objekte geprüft. E get(int index) gibt den Wert an dem angegebenen Index zurück. Wirft bei ungültigem Index eine IndexOutOfBoundsException. 22 / 78 Methoden von List<E> (Forts.) int indexof(object o) gibt den Index des ersten Auftretens eines Listenelements an, das zu o äquivalent ist, sonst 1. boolean isempty() ist wahr, wenn die Liste leer ist. int lastindexof(object o) gibt den Index des letzten Auftretens eines Listenelements an, das zu o äquivalent ist, sonst 1. E remove(int index) entfernt das Element an Position index und gibt das entfernte Element zurück. boolean remove(object o) entfernt das erste Element, das zu o äquivalent ist (so vorhanden). Gibt true zurück, falls ein Element entfernt wurde. 23 / 78 Methoden von List<E> (Forts.) E set(int index, E e) setzt den Listenwert an Position index aus e, gibt dann den alten Wert zurück. int size() Anzahl der Listenelemente. List<E> sublist(int start, int end) gibt eine List mit den Elementen von start (inkl.) bis end (exkl.) zurück. Object[] toarray() gibt alle Elemente der Liste als Array vom Typ Object[] zurück. T[] toarray(t[] a) füllt den übergebenen Array vom Typ T[] mit allen Elementen der Liste oder erzeugt einen neuen, wenn die Größe des Array nicht ausreicht. Rückgabewert ist der gefüllte Array. 24 / 78 Handout S. 8

Beispiele für Listenoperationen p u b l i c Bsp ( ) throws H e adlessexception { super ( ) ; JTextField eingabe = new JTextField ( ) ; eingabe. settooltiptext ( " Text eingeben " ) ; 25 / 78 ArrayList oder LinkedList Die beiden Klassen ArrayList und LinkedList implementieren beide das Interface List und sind daher einfach gegeneinander austauschbar. Welche der beiden Implementierungen von List verwendet wird, hängt von den Anforderungen der Anwendung ab: ArrayList hat Vorteile, wenn häufig ein Zugriff auf Elemente über den Index statt findet. Einfügen und Löschen am Ende ist schnell, das Einfügen und Löschen an einer beliebigen Position kann allerdings aufwendig sein (im Mittel linearer Aufwand). LinkedList ist bei einem Zugriff auf die Elemente über einen Index im Nachteil. Dafür ist das eigentliche Einfügen bzw. Löschen schnell und im Aufwand konstant, unabhängig von Position und Größe der Liste. I. A. ist der Speicherbedarf für LinkedList größer. 26 / 78 Von List zu Array... Mit der Methode Object[] toarray() wird ein Array vom Typ Object[] erzeugt, d. h. beim Zugriff auf die Array-Elemente ist meist ein expliziter Cast notwendig. Hier hilft die zweite Variante T[] toarray(t[]). Diese wirkt etwas seltsam, weil sie einen Array vom Typ T[] als Parameter erwartet. Der so übergebene Array wird mit den Listenelementen gefüllt, sofern der Array hinreichend groß ist. Ansonsten wird stattdessen ein neuer Array angelegt. Rückgabewert ist in jedem Fall ein Array vom Typ T[]. Häufig sieht man Ausdrücke der Art Double[] a = l.toarray(new Double[0]); 27 / 78 Handout S. 9

... und zurück Für den Weg von einem Array zu einer Liste existiert in java. util.arrays die statische Methode aslist (...). Diese kann wahlweise mit einem Array oder mit mehreren Parametern aufgerufen werden und legt darüber die Implementierung einer List. Diese kann dann z. B. dem Konstruktor von ArrayList oder LinkedList übergeben werden. Beispiel: Integer [] array = {4, 7, 13, -5; ArrayList<Integer> list1 = LinkedList<String> list2 = new ArrayList<Integer>(Arrays.asList(array)); new LinkedList<String>(Arrays.asList("Hallo","Echo")); 28 / 78 Hierarchie von Collections Die sequenziellen Container (ArrayList & LinkedList) und Mengen implementieren das Interface Collection. Die assoziativen Container werden zwar auch häufig zu den Collections gezählt, sind aber tatsächlich unabhängig vom Interface Collection, sondern implementieren in Wirklichkeit das Interface Map. 29 / 78 Schaubild 30 / 78 Handout S. 10

Das Interface Collection<E> Ein großer Teil der Methoden aus dem Interface List der letzten Vorlesung stammen bereits aus Collection boolean add(e e) fügt ein Objekt e hinzu. void clear() löscht alle Werte aus der COllection. boolean contains(object o) prüft, ob es ein Element e gibt, für das der Ausdruck e.equals(o) wahr ist. boolean isempty() ist wahr, wenn die COllection leer ist. Iterator<E> iterator() gibt einen Iterator zurück. Dies ist ein bisher noch nicht behandeltes Sprachelement, das im Weiteren näher behandelt wird. 31 / 78 Das Interface Collection<E> boolean remove(object o) entfernt das erste Element, das zu o äquivalent ist. true, falls ein Element entfernt wurde. int size() Anzahl der Elemente. Object[] toarray() gibt alle Elemente der COllection als Array vom Typ Object[] zurück. T[] toarray(t[] a) füllt den übergebenen Array vom Typ T[] mit allen Elementen oder erzeugt einen neuen, wenn die Größe des Array nicht ausreicht. Rückgabewert ist der gefüllte Array. 32 / 78 Grundfunktionalität von Collections Beim Betrachen der Methoden werden die wesentlichen Gemeinsamkeiten aller Collections deutlich: Hinzufügen eines Objekts (add), Entfernen eines Objekts (remove), Prüfen, ob ein Objekt enthalten ist (contains) Indexbezogene Operationen kommen erst in List hinzu. Frage: Aber wie kann man z. B. alle Elemente einer Collection ausgeben? 33 / 78 Handout S. 11

Iteratoren Motivation Betrachten zunächst folgendes Beispiel: L i s t <Double> l = new LinkedList <Double >(); // F ü l l e n double summe = 0 ; f o r ( i n t i =0; i <l. s i z e ( ) ; i ++) { summe += l. g e t ( i ). doublevalue ( ) ; Wie ist das Laufzeitverhalten der for-schleife? Die Zugriffszeit auf das Element i ist proportional zu i, die Laufzeit der Iteration daher proportional zu n 1 i=0 i. Das Ergebnis dieses Ausdruck verhält sich quadratisch, also ist die Iteration von der Ordnung n 2 (hierbei ist n die Länge der Liste). 34 / 78 Alternative Fassung Vergleichen wir das Verhalten der Summation über Indexzugriff mit folgender Fassung: L i s t <Double> l = new LinkedList <Double >(); // F ü l l e n double summe = 0 ; f o r ( Double d : l ) { summe += d. doublevalue ( ) ; Hier stellt man fest, dass die Laufzeit deutlich besser ist, denn in der for-schleife wird tatsächlich von Element zu Element gesprungen (während bei der Summation über Index bei jedem Schleifendurchlauf das passende Element jeweils neu abgezählt wird). Die Laufzeit ist somit insgesamt von der Ordnung n, wächst also linear mit der Länge der Liste. 35 / 78 Iteratoren Tatsächlich wird der Code for (Double d : l ) folgt umgesetzt: {... intern wie f o r ( I t e r a t o r <Double> i t e r=l. i t e r a t o r ( ) ; i t e r. hasnext ( ) ; ) { Double d = i t e r. next ( ) ; //... Ein Iterator ist dabei ein Zeiger, der über alle Elemente einer Collection iterieren kann. Dieser besitzt die Methoden bool hasnext() Gibt es ein nachfolgendes Element? E next() holt den Wert des jeweils nächsten Elements. void remove() entfernt das zuvor mit next() geholte Element aus der darunterliegenden Collection (optional). In der bildlichen Vorstellung zeigt der Iterator immer zwischen zwei Elemente! 36 / 78 Handout S. 12

Anwendung Folgendes Programm simuliert die Ziehung der Lottozahlen (als Vorgeschmack auf Sets): C o l l e c t i o n <I n t e g e r > c = new TreeSet<I n t e g e r >(); c. add ( 4 3 ) ; c. add ( 7 ) ; c. add ( 2 ) ; c. add ( 2 9 ) ; c. add ( 3 3 ) ; c. add ( 1 2 ) ; f o r ( I t e r a t o r <I n t e g e r > i t e r=c. i t e r a t o r ( ) ; i t e r. hasnext ( ) ; ) { I n t e g e r z a h l = i t e r. next ( ) ; System. out. p r i n t l n ( z a h l ) ; 37 / 78 Iterator ListIterator Der normale Iterator hat zwei Nachteile: Die Iteration ist nur in eine Richtung möglich. Über den Iterator können keine Elemente der darunter liegenden Collection ausgetausch werden. Für das Interface List gibt es einen erweiterten Iterator, den ListIterator. Dieser erlaubt Iterationen in beide Richtungen, Zugriff auf den Index eines Elementes und das Austauschen von Elementen. 38 / 78 Schaubild von ListIterator<E> Quelle: Ullenboom, Java ist eine Insel, 7. Aufl. 39 / 78 Handout S. 13

Das Interface ListIterator<E> Für ListIterator <E> kommen folgende Methoden hinzu: boolean hasprevious() Gibt es ein Vorgängerelement? int nextindex() Index des nächsten Elements. E previous() holt das Vorgängerelement. int previousindex() Index des vorigen Elements. void set(e e) ersetzt das beim letzten next() oder previous() geholte Element durch e. 40 / 78 Beispiel Im folgenden Beispiel werden in einer String-Liste alle Vorkommen von Klaus durch Otto ersetzt: L i s t <S t r i n g > l = new LinkedList <S t r i n g >(); // F ü l l e n L i s t I t e r a t o r <S t r i n g > i t e r ; f o r ( i t e r=l. l i s t I t e r a t o r ( ) ; i t e r. hasnext ( ) ; ) { S t r i n g wert = i t e r. next ( ) ; i f ( wert==" Klaus " ) { i t e r. s e t ( " Otto " ) ; System. out. p r i n t l n ( " Element an Index "+ i t e r. p r e v i o u s I n d e x ()+" e r s e t z t " ) ; 41 / 78 Mengentypen: Set Ein Set (deutsch: Menge) in Java ist ein Interface, das von Collection erbt. Im Gegensatz zu List kommen hier aber keine neuen Methoden hinzu. Eine Implementation von Set unterstützt daher im Wesentlichen folgende Operationen: Hinzufügen neuer Element Entfernen von Elementen Prüfen darauf, ob ein Element vorhanden ist Hierbei gilt, dass ein einem Set niemals doppelte Einträge gibt, also für alle Paare e1 und e2 der Ausdruck e1.equals(e2) unwahr ist. 42 / 78 Handout S. 14

Sets und Sortierung Wie bei allen Collections ermöglicht die Methode iterator () eine Iteration über alle Elemente des Sets, was z. B. implizit benutzt wird durch: for (E e : myset) {... Normale Sets stellen dabei keinen Anspruch auf die Reihenfolge der Elemente bei der Iteration. Diese muss keiner Ordnung unterliegen (wie aufsteigend), noch muss diese reproduzierbar sein. Demgegenüber stellt das Interface SortedSet sicher, dass die Elemente immer anhand einer festen (vorgegebenen) Ordnung sortiert sind. 43 / 78 Die Implementierung HashSet Häufig wird für Mengen die Implementierung HashSet verwendet. Der grundsätzliche Aufbau soll durch eine einfache Analogie verdeutlicht werden. In der CD-Abteilung eines Kaufhauses werden die CDs nach folgendem System geordnet: Das Sortiment ist in Regale für einzelne Musikrichtungen unterteilt, im Regal einer Musikrichtung gibt es (je nach Bedeutung des/der Interpreten) eine Unterteilung nach einzelnen Interpreten bzw. einer Gruppe von Interpreten mit gleichem Anfangsbuchstaben, innerhalb der Unterteilung sind die CDs ungeordnet. 44 / 78 Beispiel Beispiel 1: U2, The Joshua Tree findet man unter Rock & Pop, dort unter U2, dann muss man alle CDs von U2 durchsuchen, bis man die richtige gefunden hat. Beispiel 2: Mambo Kurt, Back in Beige unter Rock & Pop, dort unter M diverse, und dort durchblättern, bis man die CD findet (sehr unwahrscheinlich!) Diese Ordnung entspricht einer Sortierung nach Hashes, hier auf zwei Ebenen: Kategorie und Interpret. Die Hashes engen die Suche ein, innerhalb der ungeordneten Menge von CDs mit gleichen Hashes hilft aber nur lineare Suche. Die Aufteilung nach Hashes ist dabei dynamisch. Veröffentlicht ein Interpret viele CDs, kriegt er irgendwann sein eigenes Fach! 45 / 78 Handout S. 15

Technische Funktionsweise von HashSet<E> HashSet<E> unterteilt die Elemente (anhand einer Hash-Funktion) in n verschiedene Kategorien, mit den entsprechenden Indizes 0... n 1, und ordnet jedem dieser Indizes ein Bucket (Kübel) zu. Hierin liegen die Elemente mit gleicher Kategorie ungeordnet. Die Hash-Funktion greift dabei auf die Methode int hashcode() aus Object zurück. Besondere Eigenschaft hierbei ist, dass zunächst die Unterteilung der Hashwerte dabei sehr grob ist (wenig Buckets), und bei wachsender Zahl von Elementen die Unterteilung nach Hashwerten verfeinert wird (mehr Buckets), um die Zahl der Elemente in einem Bucket zu begrenzen. Dies nennt man Rehashing. 46 / 78 Zustandsparameter eines HashSet Der interne Zustand eines HashSet<E> wird durch zwei Parameter beschrieben: Kapazität (capacity) gibt die Zahl der Buckets an, d. h. je größer die Kapazität, desto feiner die Unterteilung in Hash-Kategorien. Füllfaktor (load factor) gibt das aktuelle Verhältnis der Gesamtzahl der Elemente zur Kapazität an. Dabei ist immer die Bedingung size load factor capacity erfüllt, der Füllfaktor bestimmt also, wann ein Rehashing (meist auf doppelte Kapazität) durchgeführt wird. Üblicher Wert für den Füllfaktor ist 0.75. 47 / 78 Laufzeitverhalten Bezüglich des Laufzeitverhaltens verhalten sich Kapazität und Füllfaktor bei verschiedenen Operationen gegenläufig: Bei den Operationen add, remove und contains ist der Zugriff auf das passende Bucket von konstanter Laufzeit, das Durchsuchen der Elemente des Buckets (auch bei add zur Duplikatsprüfung) ist proportional zur mittleren Größe des Buckets (Size/Capacity), die durch den Load Factor nach oben begrenzt wird. Daher ist die Laufzeit im Prinzip konstant unter der Annahme einer homogenen Verteilung der Elemente auf die Hashes, wobei hier kleine Füllfaktoren vorteilhaft sind. Eine Iteration über alle Elemente braucht einen Iterationsschritt pro Bucket zzgl. einen je Element im Bucket, somit also Capacity (Size/Capacity + 1) = Capacity + Size 48 / 78 Handout S. 16

HashSet mit eigener Klasse Wie kann man einen HashSet für eine eigene Klasse benutzen, z. B. p u b l i c c l a s s Person { p r i v a t e S t r i n g nachname ; p r i v a t e S t r i n g vorname ; p r i v a t e i n t g e b u r t s j a h r ; p u b l i c Person ( S t r i n g nachname, S t r i n g vorname, i n t g e b u r t s j a h r ) { t h i s. nachname = nachname ; t h i s. vorname = vorname ; t h i s. g e b u r t s j a h r = g e b u r t s j a h r ; 49 / 78 Test-Programm Zum Testen verwenden wir den folgenden Code: Person p1 = new Person ( " Meier ", " Karl ", 1 9 5 5 ) ; Person p2 = p1 ; Person p3 = new Person ( " Meier ", " Karl ", 1 9 5 5 ) ; Set<Person> s e t = new HashSet<Person >(); System. out. p r i n t l n ( s e t. add ( p1 ) ) ; // t r u e System. out. p r i n t l n ( s e t. add ( p2 ) ) ; // f a l s e System. out. p r i n t l n ( s e t. add ( p3 ) ) ; // t r u e System. out. p r i n t l n ( s e t. c o n t a i n s ( new Person ( " Meier ", " Karl ", 1 9 5 5 ) ) ) ; // f a l s e 50 / 78 Analyse Die Klasse Person überlädt nicht die Methode equals aus Object. Nach der Implementation von equals in Object sind zwei Objekte aber genau dann gleich, wenn sie identisch sind. Daher lässt sich p 2 nicht zum Set hinzufügen, da es mit p 1 identisch ist und daher beide gleich sind; p 3 wird hingegen schon, da es als eigene Instanz von Person nicht identisch und daher auch nicht äquivalent zur existierenden Person im Set ist. Schließlich befinden sich also zwei Elemente im Set. Die Suche nach der Person geht schief, da die zur Suche verwendete Instanz ungleich (da nicht identisch) zu allen im Set vorhandenen Personen ist. Es fehlt daher die Definition, dass Personen mit gleichem Vor-, Nachnamen und Geburtsjahr gleich sind. 51 / 78 Handout S. 17

Definition von Gleichheit @Override p u b l i c boolean e q u a l s ( Object obj ) { i f ( t h i s == obj ) r e t u r n t r u e ; i f ( obj == n u l l ) r e t u r n f a l s e ; i f ( g e t C l a s s ( )!= obj. g e t C l a s s ( ) ) r e t u r n f a l s e ; f i n a l Person o t h e r = ( Person ) obj ; i f ( g e b u r t s j a h r!= o t h e r. g e b u r t s j a h r ) r e t u r n f a l s e ; i f ( nachname == n u l l ) { i f ( o t h e r. nachname!= n u l l ) r e t u r n f a l s e ; e l s e i f (! nachname. e q u a l s ( o t h e r. nachname ) ) r e t u r n f a l s e ; i f ( vorname == n u l l ) { i f ( o t h e r. vorname!= n u l l ) r e t u r n f a l s e ; e l s e i f (! vorname. e q u a l s ( o t h e r. vorname ) ) r e t u r n f a l s e ; r e t u r n t r u e ; 52 / 78 equals und hashcode Mit dieser Definition von equals sind zwei Personen gleich, wenn ihre Attribute gleich sind (wobei per Konvention equals( null) immer false ergibt). Aber funktioniert der HashSet nun? Nein! Der HashSet verwendet zur Kategorisierung die Methode hashcode. Diese wird in Object so definiert, dass sie von der Speicheradresse des Objekts abhängt. Daher werden Objekte, obwohl gleich (nach Definition von equals), trotzdem unterschiedlich einsortiert, da ihr Hash-Code verschieden sein kann. Dies führt dazu, dass Dubletten beim Einfügen und Elemente beim Suchen nicht erkannt werden! 53 / 78 Überladen von hashcode In der bisherigen Definition von Person wurde eine wichtige Regel verletzt: Zwei Instanzen, die gleich sind, müssen den selben Hash-Code liefern! Hingegen ist es keine formale Bedingung, dass ungleiche Instanzen einen unterschiedlichen Hash-Code haben allerdings wäre es keine gute Idee, die Methode einfach durch public void hashcode() { return 0; zu überladen. Ein HashSet für solche Objekte würde dann zwar funktionieren, aber er wäre maximal ineffizient, weil alle Elemente in der selben Kategorie einsortiert würden. Es empfiehlt sich daher, hashcode so zu überladen, dass es sich an den signifikanten Attributen orientiert. 54 / 78 Handout S. 18

Strategien zur Hash-Code-Berechnung Attribut a Hash-Code byte, char, short, int ( int) a long (int )(a ^ (a >>> 32)) float Float. oattointbits (a) double long bits = Double.doubleToLongBits(a); (int )( bits ^ (bits >>> 32)) boolean a? pattern1 : pattern2 Object null == a? 0 : a.hashcode() Bei mehreren Attributen mit result = 1 beginnen und für jedes Attribut result = 31 * result + hash rechnen (mit hash nach obiger Tabelle) 55 / 78 Implementierung von hashcode Nach dem vorigen Schema erhält man: @Override p u b l i c i n t hashcode ( ) { f i n a l i n t prime = 3 1 ; i n t r e s u l t = 1 ; r e s u l t = prime * r e s u l t + g e b u r t s j a h r ; r e s u l t = prime * r e s u l t + ( ( nachname == n u l l )? 0 : nachname. hashcode ( ) ) ; r e s u l t = prime * r e s u l t + ( ( vorname == n u l l )? 0 : vorname. hashcode ( ) ) ; r e t u r n r e s u l t ; Faustregel: Die Attribute, die signifikant für die Bestimmung der Gleichheit sind, werden auch für die Berechnung des Hash-Codes verwendet. 56 / 78 SortedSet Einführung Der normale Set hat einen Nachteil: Bei einer Iteration über alle Elemente des Sets sind diese ungeordnet. Tatsächlich ist sogar die Reihenfolge der Iteration nicht zwingend reproduzierbar (und ändert sich z. B. beim Rehashing). Dieser Nachteil wird durch das Interface SortedSet behoben. Allerdings stellt SortedSet strengere Anforderungen an die Elemente: Für diese muss nun nicht mehr nur die Gleichheit definiert sein, sondern auch eine Ordnungsfunktion, die eine Kleiner-/Größer-Beziehung liefert. 57 / 78 Handout S. 19

Ordnungsfunktionen Damit eine Klasse für einen SortedSet verwendet werden kann, muss sie: Entweder eine natürliche Ordnung besitzen, indem die Klasse das Interface Comparable<E> implementiert (und die zugehörige Methode int compareto(e e)) oder dem SortedSet im Konstruktor eine Implementierung des Interface Comparator<? super E> übergeben wird (wo dann die Methode int compare(? e1,? e2) implementiert wird) Hierbei gilt: < 0 falls e 1 < e 2 e1.compareto(e2), compare(e1,e2) 0 falls e 1 = e 2 > 0 falls e 1 > e 2 58 / 78 TreeSet als Implementierung Die Klasse TreeSet<E> implementiert das Interface SortedSet<E>. In diesem Interface kommen (zusätzlich dazu, dass eine Iteration über die Elemente nun sortiert ist) zwei neue Methoden hinzu: E first() liefert das erste Element der Menge, E last() liefert das letzte Element der Menge. Wird dem Konstruktor kein Comparator als Parameter übergeben, so wird die natürliche Ordnung verwendet. Hierfür muss die Klasse, für die der TreeSet benutzt wird, Comparable implementieren. Ein TreeSet ist als binärer Baum implementiert. Die Laufzeit für add, remove bzw. contains ist von der Ordnung log(n). 59 / 78 Beispiel 1: Personen vergleichbar machen c l a s s Person implements Comparable<Person> {... @Override p u b l i c i n t compareto ( Person o t h e r ) { i n t r e s u l t = nachname. compareto ( o t h e r. nachname ) ; i f ( r e s u l t!= 0) r e t u r n r e s u l t ; r e s u l t = vorname. compareto ( o t h e r. vorname ) ; i f ( r e s u l t!= 0) r e t u r n r e s u l t ; r e s u l t = I n t e g e r. valueof ( g e b u r t s j a h r ). compareto ( o t h e r. g e b u r t s j a h r ) ; r e t u r n r e s u l t ; 60 / 78 Handout S. 20

Beispiel 2: Eigener Comparator import java. u t i l. Comparator ; p u b l i c c l a s s AbsCompare implements Comparator<Number> { p u b l i c i n t compare ( Number x, Number y ) { double xd = x. doublevalue ( ) ; double yd = y. doublevalue ( ) ; r e t u r n Double. valueof ( xd>=0? xd : - xd ). compareto ( yd>=0? yd : - yd ) ; 61 / 78 Beispiel 2: Anwendung import java. u t i l. * ; p u b l i c c l a s s Main { p u b l i c s t a t i c void main ( S t r i n g [ ] a r g s ) { SortedSet <I n t e g e r > s e t = new TreeSet<I n t e g e r >(new AbsCompare ( ) ) ; s e t. add ( - 3 ) ; s e t. add ( 5 ) ; s e t. add ( - 8 ) ; s e t. add ( 2 ) ; s e t. add ( 0 ) ; f o r ( I n t e g e r i : s e t ) { System. out. p r i n t l n ( i ) ; // Ausgabe : 0 2-3 5-8 62 / 78 NavigableSet Die Klasse TreeSet<E> implementiert seit Java 6 nicht nur SortedSet<E>, sondern sogar das neue Interface NavigableSet<E>. Dieses bietet einige Zusatzoperationen: NavigableSet<E> subset(e from, E to) liefert alle Elemente e mit from e < to. E floor(e e) liefert das größte Element, das e ist. E ceiling(e e) liefert das kleinste Element, das e ist. E lower(e e) liefert das größte Element, das < e ist. E higher(e e) liefert das kleinste Element, das > e ist. Beispiel: Für einen TreeSet<String> set liefert set.subset("c","d") alle Elemente, die mit C beginnen. 63 / 78 Handout S. 21

Assoziative Container Assoziative Container verknüpfen einen Schlüssel (key) mit einem Wert (value). Telefonbuch (Name Telefonnr.) Wörterbuch (deutsch englisch) Kfz (Kennzeichen Halter) Personalnummer Mitarbeiter Buchkataloge (ISBN Buch, Autor Bücher) Obige Beispiele enthalten Zuordnungen der Typen 1 zu 1 und 1 zu n. Java unterstützt nur eindeutige Assoziationen, d. h. zu einem Schlüssel gehört maximal ein assoziierter Wert. 64 / 78 Die Interfaces für assoziative Container Obwohl assoziative Container häufig auch als Collections bezeichnet werden, sind diese vom Interface Collection tatsächlich unabhängig. Assoziative Container werden durch das Interface Map repräsentiert. Dieses ist generisch, mit zwei Parameterklassen einer für den Schlüsseltyp, einer für den Wertetyp Hiervon erbt das Interface SortedMap, bei dem die Einträge nach den Schlüsseln sortiert werden. Weiterhin gibt es seit Java 6 das Interface NavigableMap. 65 / 78 Klassen-Diagramm «interface» Map +put(key : K, value : V) : V +get(key : K) : V +remove(o : Object) : V +containskey(key : K) : boolean +containsvalue(value : V) : boolean +clear() : void +size() : int +isempty() : boolean +keyset() : Set<K> +values() : Collection<V> +entryset() : Set<Entry<K,V> > K, V «interface» Entry +getkey() : K +getvalue() : V +setvalue(value : V) : V K, V «interface» SortedMap +firstkey() : K +lastkey() : K +headmap(tokey : K) : SortedMap<K,V> +tailmap(fromkey : K) : SortedMap<K,V> +submap(fromkey : K, tokey: K) : SortedMap<K,V> K, V 66 / 78 Handout S. 22

Realisierung Im Prinzip sind Maps sehr ähnlich zu Sets. So sind diese im Wesentlichen Maps nichts anderes als Sets vom Typ Map.Entry<K,V>, wobei für die Eindeutigkeit der Mengenelemente nur der Schlüssel berücksichtigt wird. Dies bedeutet: Jeder Schlüssel darf nur einmal vorkommen (bezogen auf Äquivalenz mit equals). Bei selbst definierten Klassen müssen die Methoden equals (und hashcode bei Unterteilung nach Hashes) und compareto (bei sortierten Maps) für den Schlüsseltyp sinnvoll definiert sein. Werte dürfen demgegenüber beliebig häufig vorkommen. 67 / 78 Methoden von Map Das Interface Map<K,V> definiert u. a. folgende Methoden V put(k key, V val) assoziiert den Wert val mit dem Schlüssel key. Gab es vorher schon einen Wert für key, so wird dieser überschrieben (und der alte Wert als Rückgabewert zurückgegeben). Gab es vorher noch keine Assoziation für key, so ist der Rückgabewert null. V get(k key) gibt den mit key assoziierten Wert zurück (sonst null). V remove(k key) entfernt die Assoziation für key. Rückgabewert ist der zuvor assoziierte Wert (null, falls keine existierte). 68 / 78 Methoden von Map (Forts.) boolean containskey(k key) gibt true zurück, falls der Schlüssel existiert. boolean containsvalue(v val) gibt true zurück, falls ein Schlüssel den angegebenen Wert assoziiert. void clear() löscht alle Einträge. int size() gibt die Zahl der Schlüssel-Wert-Paare zurück. boolean isempty() gibt true bei einem leeren Container zurück. 69 / 78 Handout S. 23

Methoden von Map (Forts.) Set<K> keyset() gibt die Menge aller Schlüssel zurück. Collection<V> values() gibt alle vorhandenen Werte als Collection zurück. Die Reihenfolge entspricht dabei der Reihenfolge der Schlüssel bei einer Iteration über keyset(). Der Rückgabewert ist eine Collection, da jeder Wert prinzipiell beliebig häufig vorkommen darf. Set<Map.Entry<K,V> > entryset() gibt alle Schlüssel-Wertpaare als Set zurück. 70 / 78 Das Interface Map.Entry Über die Methode entryset erhält man eine Mengenansicht (set view) der Schlüssel-Werte-Paare. Hierfür wird das innere Interface Entry<K,V> von Map<K,V> verwendet. Dieses kennt drei Methoden: K getkey() gibt den Schlüssel zuück. V getvalue() gibt den Wert zurück. V setvalue(v val) setzt den Wert neu und gibt den vorherigen als Rückgabewert zurück. 71 / 78 Implementierung Die Implementierungen von Map sind analog zu Set: HashMap teilt die Schlüssel-Werte-Paare anhand des Hash-Codes der Schlüssel in Buckets auf. Da die Hash-Codes der Schlüssel keine Ordnung definieren, sind die Schlüssel unsortiert. Beim Wachsen der HashMap kommt es zu einem Rehashing, wenn der Füllgrad den definierten load factor übersteigt. TreeMap implementiert zusätzlich Sorted-/NavigableMap. Es wird ein binärer Baum anhand der Schlüssel verwendet. Daher muss ein Comparator für den Schlüsseltyp angegeben werden, oder es wird die natürliche Ordnung der Schlüssel verwendet (hierfür muss Comparable implementiert sein). 72 / 78 Handout S. 24

Beispiel import java. u t i l. * ; p u b l i c c l a s s Bsp1 { p u b l i c s t a t i c void main ( S t r i n g [ ] a r g s ) { Map<S t r i n g, S t r i n g > t e l e f o n = new HashMap<S t r i n g, S t r i n g >(); t e l e f o n. put ( "Mustermann, Karl ", " 6835454 " ) ; t e l e f o n. put ( "Ahrens, Karl ", " 484894 " ) ; t e l e f o n. put ( " F i s c h e r, Maria ", " 132542 " ) ; t e l e f o n. put ( "Mustermann, Karl ", " 944889 " ) ; f o r (Map. Entry<S t r i n g, S t r i n g > e n t r y : t e l e f o n. e n t r y S e t ( ) ) { System. out. p r i n t l n ( e n t r y. getkey ()+" : "+ e n t r y. getvalue ( ) ) ; 73 / 78 Analyse Im vorigen Beispiel werden vier Einträge der Telefonliste hinzu gefügt, aber nur drei davon befinden sich am Ende im Container. Für den Schlüssel Mustermann, Karl werden zwei Nummern hinzugefügt. Schlüssel in der Map müssen aber eindeutig sein. Daher befindet sich am Ende nur einer der beiden Einträge in der Map. Die Logik bei doppelten Einträgen bei Map unterscheidet sich vom Set: Bei Set gewinnt das erste add, weitere scheitern und geben false zurück. Bei Map wird jeweils der alte Wert überschrieben, es gewinnt also das letzte put. 74 / 78 Map mit eigener Klasse als Schlüssel Für eine Map kann auch eine eigene Klasse als Schlüssel verwendet werden. Hierbei gelten sinngemäß die selben Kriterien wie bei den Mengen: Für HashMap muss die Klasse equals und hashcode sinnvoll überladen, für TreeMap muss die Klasse entweder Comparable implementieren oder ein geeigneter Comparator existieren. 75 / 78 Handout S. 25

Neue Version der Telefonliste import java. u t i l. * ; p u b l i c c l a s s Bsp2 { p u b l i c s t a t i c void main ( S t r i n g [ ] a r g s ) { Map<Person, S t r i n g > t e l e f o n = new HashMap<Person, S t r i n g >(); t e l e f o n. put ( new Person ( "Mustermann", " Karl ", 1 9 7 3 ), " 6835454 " ) ; t e l e f o n. put ( new Person ( " Ahrens ", " Karl ", 1 9 5 0 ), " 484894 " ) ; t e l e f o n. put ( new Person ( " F i s c h e r ", " Maria ", 1 9 6 6 ), " 132542 " ) ; t e l e f o n. put ( new Person ( "Mustermann", " Karl ", 1 9 7 3 ), " 944889 " ) ; f o r (Map. Entry<Person, S t r i n g > e n t r y : t e l e f o n. e n t r y S e t ( ) ) { System. out. p r i n t l n ( e n t r y. getkey ()+" : "+ e n t r y. getvalue ( ) ) ; 76 / 78 Das Interface SortedMap Bei SortedMap sind die Paare nach dem Schlüssel sortiert. Zusätzlich existieren drei Methoden, eine Teilmenge der Schlüssel auszuwählen: headmap(k key) alle Einträge vor dem angegebenen Schlüssel, tailmap(k key) alle Einträge ab dem Schlüssel (einschließlich), submap(k from, K to) alle Einträge mit Schlüssel zwischen from (inkl.) und to (exkl.). Rückgabetyp ist jeweils SortedMap<K,V>. Ab Java 6 implementiert TreeMap das neue Interface NavigableMap, das zusätzliche Methoden bietet. 77 / 78 Beispiel SortedMap<S t r i n g, S t r i n g > t e l e f o n = new TreeMap<S t r i n g, S t r i n g >(); // F ü l l e n f o r (Map. Entry<String, String > entry : t e l e f o n. tailmap ( "F" ). e n t r y S e t ( ) ) { System. out. p r i n t l n ( e n t r y. getkey ()+" : " +e n t r y. getvalue ( ) ) ; f o r ( S t r i n g name : t e l e f o n. submap ( "B", "H" ). keyset ( ) ) { System. out. p r i n t l n ( "Name : "+name ) ; 78 / 78 Handout S. 26