Java Collection Framework Programmiermethodik Eva Zangerle Universität Innsbruck Überblick Einführung Java – Ein erster Überblick Objektorientierung Vererbung und Polymorphismus Ausnahmebehandlung Pakete und Javadoc Spezielle Themen Generische Programmierung Einleitung Ausgewählte Klassen Java Collection-Framework Streams Unit-Tests Iteratoren Wichtige Themen Entwurfsmuster - Eine Einführung GUI-Programmierung Java Virtual Machine Ausblick Programmiermethodik - Java Collections 2 Einleitung Motivation • Programmierer benötigt sehr oft bestimmte Datenstrukturen. Datenstrukturen sollten schon vorhanden sein. Datenstrukturen sollten generisch sein. Allgemeine Algorithmen (anwendbar auf unterschiedliche Datenstrukturen) sollten vorhanden sein. • Lösung? Java Collection-Framework Programmiermethodik - Java Collections 4 Grundlagen • Collection-Framework befindet sich im Paket java.util. • Für unterschiedliche Datenstrukturen stehen unterschiedliche Interfaces zur Verfügung. Die Interfaces geben die Schnittstellen für den Zugriff auf die Datenstrukturen an (z.B. Einfügen in Listen). Die konkrete Verwaltung wird durch konkrete Implementierungen dieser Interfaces bestimmt. Seit Java 1.5 ist das Collection-Framework generisch. • Jedes Interface kann durch unterschiedliche Implementierungen unterstützt werden (z.B. LinkedList, ArrayList). • Es gibt Algorithmen, die auf Collection-Objekte (Listen etc.) angewandt werden können (z.B. Sortieren). Programmiermethodik - Java Collections 5 Interfaces (Überblick) • Interfaces sind die Grundlage (java.util.Collection oder java.util.Map) Basisoperationen (Hinzufügen, Finden, etc.) Mengenoperationen (andere Collection einfügen) Feldoperationen (z.B. andere Ansichten auf key/value) • Applikationen sollten ihren Code ausschließlich auf Interfaces aufbauen Austauschbarkeit. • Interfaces bilden Hierarchien. Programmiermethodik - Java Collections 6 Collection – Interfaces (Seit 1.5) Collection • Eine Collection verwaltet Objekte (Elemente). • Interface enthält generelle Methoden (für alle Collection-Typen). • Es gibt keine direkte Implementierung dieses Interfaces. Set • Eine Collection die keine duplizierten Elemente enthält. List • Eine geordnete Collection (mit duplizierte Elementen) • Elemente können über einen Index angesprochen werden (nicht immer effizient). Queue Map SortedSet und SortedMap Programmiermethodik - Java Collections • Verwaltung von Warteschlangen (ähnlich zu Listen) • FIFO, Prioritätswarteschlangen • Maps verwalten Schlüssel mit dazugehörigen Werten. • Schlüssel sind eindeutig. • Jeder Schlüssel kann einen dazugehörigen Wert haben. • Sind spezielle Versionen von Set und Map, bei denen die Elemente (Schlüssel) in aufsteigender Reihenfolge verwaltet werden. 7 Implementierung • Es gibt unterschiedliche Ansätze für die Implementierung von konkreten Collection-Klassen: Generelle Implementierungen, die häufig in Applikationen verwendet werden. Spezielle Implementierungen Nebenläufige Implementierungen (für Multithreaded-Applikationen, siehe Entwurf von Softwaresystemen). Wrapper-Implementierungen, die mit anderen Implementierungen benutzt werden (oft generelle Implementierungen) und zusätzliche oder eingeschränkte Funktionalität zur Verfügung stellen. Einfache effiziente Implementierungen (Mini-Implementierungen) für spezielle Collections. Abstrakte Implementierungen, die ein Implementierungsskelett zur Verfügung stellen und damit die Implementierung eigener CollectionKlassen erleichtern. Programmiermethodik - Java Collections 8 Generelle Implementierungen (1) Implementierungen Interface Hashtabellen Set HashSet List Dynamische Arrays Bäume TreeSet ArrayList HashTabellen + Verkettete Listen LinkedHashSet LinkedList Queue Map Verkettete Listen LinkedList HashMap Programmiermethodik - Java Collections TreeMap LinkedHashMap 9 Generelle Implementierungen (2) • Es existieren generelle Implementierungen für die Interfaces Set, List und Map. • Interfaces SortedSet und SortedMap werden von TreeSet bzw. TreeMap zusätzlich implementiert. • Queue-Interface wird z.B. von den folgenden Klassen implementiert: LinkedList – FIFO Warteschlangen PriorityQueue – Prioritätswarteschlange (ist eher eine spezielle Implementierung) Programmiermethodik - Java Collections 10 Seit Java 1.6 • Deque (Double ended queue) Einfügen und Löschen an beiden Enden Wird u.a. von der Klasse LinkedList implementiert. • NavigableSet und NavigableMap Sollen SortedSet und SortedMap ersetzen. Sind Erweiterungen, die das Navigieren (aufsteigend, absteigend) erlauben. • Zusätzliche nebenläufige Implementierungen (siehe Entwurf von Softwaresystemen) • Zusätzliche Hilfsmethoden Programmiermethodik - Java Collections 11 Ausgewählte Klassen Eigenschaften ausgewählter Klassen ArrayList LinkedList • Indexzugriff auf Elemente ist überall ungefähr gleich schnell. • Einfügen und Löschen ist am Listenende schnell und wird mit wachsender Entfernung vom Listenende langsamer. • Indexzugriff auf Elemente ist an den Enden schnell und wird mit der Entfernung von den Enden langsamer. • Einfügen und Löschen ohne Indexzugriff ist überall gleich schnell. • Ansonsten abhängig vom Indexzugriff. HashSet • null-Elemente sind zulässig. • Einfügen, Suchen und Löschen sind immer gleich schnell. • Aber: Rehashing kann zu Performance-Problemen führen (siehe auch APIBeschreibung). TreeSet • null-Elemente sind nicht erlaubt. • Die Geschwindigkeit von Einfügen, Suchen und Löschen ist proportional zum Logarithmus der Anzahl der Elemente. • Auf die Elemente eines TreeSets muss eine Ordnung definiert sein (müssen vergleichbar sein, d.h. das Interface Comparable<T> implementieren). Programmiermethodik - Java Collections 13 Methoden (Beispiele) • Für Listen und Sets gibt es einige gemeinsame Methoden. • Beispiele (T ist der Elementtyp) int size() boolean add(T t) boolean remove(T t) boolean contains(T t) T get(int i) T set(int i, T t) int indexOf(T t) Programmiermethodik - Java Collections 14 Beispiel import java.util.ArrayList; import java.util.List; public final class CollectionTest1 { private CollectionTest1() { Immer kleinstmöglichen Typ verwenden! } public static void main(final String[] args) { final List<Integer> cl = new ArrayList<Integer>(); for (int x = 1; x < 10; x++) { cl.add(x); Ausgabe } [1, 2, 3, 4, 5, 6, 7, 8, 9] System.out.println(cl); 4 System.out.println(cl.get(3)); 9 System.out.println(cl.size()); System.out.println(cl.remove(2)); 3 System.out.println(cl); [1, 2, 4, 5, 6, 7, 8, 9] System.out.println(cl.contains(7)); true System.out.println(cl.set(5, 22)); 7 System.out.println(cl); [1, 2, 4, 5, 6, 22, 8, 9] System.out.println(cl.indexOf(22)); 5 } } Programmiermethodik - Java Collections 15 Maps • Arrays und Collections speichern einzelne Werte des Elementtyps (über Indexwerte adressiert, Typ der Indexwerte ist int). • Maps sind eine Verallgemeinerung von Arrays mit einem beliebigen Indextyp. Beispiel Telefonbuch Name mit Telefonnummer verknüpft. „Indextyp“ ist daher String (Name). • Map-Eintrag hat einen Schlüssel (key) und einen Wert (value). Programmiermethodik - Java Collections 16 Maps (key-value) • Jedem Schlüssel ist genau ein Wert zugeordnet. • Eine Map ist eine Menge von Schlüssel-Werte Paaren. Schlüssel müssen innerhalb einer Map eindeutig sein. Werte müssen nicht eindeutig sein. • Wie bei Sets gibt es grundsätzlich zwei Versionen. HashMap Ungeordnet Hashing TreeMap Geordnet Rot-Schwarz-Baum (balanciert) • Bestimmte Methoden wie put, get, containsKey, containsValue, remove etc. werden angeboten. Programmiermethodik - Java Collections 17 Beispiel import java.util.HashMap; import java.util.Map; public final class CollectionTest2 { public static void main(final String[] args) { final Map<String, Double> m = new HashMap<String, Double>(); m.put("PC 1", 1199.90); m.put("Spiele-PC", 2500.00); m.put("My-Cloud", 100000.00); System.out.println(m); System.out.println(m.get("PC 1")); System.out.println(m.containsKey("PC 1")); System.out.println(m.remove("PC 1")); System.out.println(m); } } Programmiermethodik - Java Collections Ausgabe: {Spiele-PC=2500.0, My-Cloud=100000.0, PC 1=1199.9} 1199.9 true 1199.9 {Spiele-PC=2500.0, My-Cloud=100000.0} 18 Maps (equals, hashcode) • HashMap und andere Klassen des Collection-Frameworks rufen die Methode equals auf, um die Gleichheit von Schlüsselobjekten (Elementen) festzustellen. • Weiters wird von einigen Klassen die Methode hashCode verwendet. • Daher sollten alle Klassen die Methoden equals und hashCode überschreiben. • Die von der Klasse Object geerbten Versionen dieser Methoden sind selten ausreichend, weil sie nur die Referenzen prüfen, den Inhalt aber ignorieren. Programmiermethodik - Java Collections 19 Maps und Collections (1) • Maps implementieren nicht das Interface Collection, sind aber Teil des Collection-Frameworks. • Maps und Collections können verknüpft werden. • Methode keySet liefert die Menge aller Schlüssel einer Map in Form eines Sets. • Methode values liefert die Werte der Map. Über die Organisation der Werte ist nichts bekannt. Das Ergebnis ist vom Typ Collection. Programmiermethodik - Java Collections 20 Maps und Collections (2) • Die Methode entrySet liefert die Menge der Einträge der Map. • Der Elementtyp ist Map.Entry<K,V> für eine Map mit dem Schlüsseltyp K und dem Werttyp V. • Map.Entry definiert die Methoden: getKey getValue • Maps kennen keine Iteratoren, Sets hingegen schon. • Daher kann man auf diesem Wege über eine Map iterieren. • Beispiel folgt! Programmiermethodik - Java Collections 21 Maps und Collections (3) • Die drei Methoden keySet, values und entrySet erzeugen keine neue Collection. • Sie liefern sogenannte Sichten („views“). • Daher sind diese Methoden auch bei großen Datenmengen sehr effizient. Es werden keine Daten kopiert! • Sichten greifen direkt auf die zugrunde liegende Map zu. • Änderungen an den Sichten wirken sich auf die darunterliegende Map aus und umgekehrt. • Wird eine Map geändert, dann wirkt sich das auf alle Sichten aus. Programmiermethodik - Java Collections 22 Iteratoren Iteratoren allgemein (1) • Durchlaufen von Collections • Iterator wird durch eine Methode (z.B. iterator) angeboten (zurückgegeben). • Ein Iterator bietet folgende Methoden an hasNext – testet, ob es weitere Elemente gibt (true) oder nicht (false). next – liefert das nächste Element und rückt den Iterator gleichzeitig um ein Element weiter, d.h. aufeinanderfolgende Aufrufe von next liefern immer neue Elemente. remove (optional) – entfernt das zuletzt zurückgegebene Element. Programmiermethodik - Java Collections 24 Iteratoren allgemein (2) • Iteratoren sind eine Verallgemeinerung von Indexwerten. • Werden ähnlich wie Indexwerte verwendet, ihr innerer Aufbau bleibt aber verborgen. • Alle Iteratoren implementieren das generische Interface Iterator<T>. • Iteratoren haben den gleichen Elementtyp wie die zugrunde liegende Collection. Programmiermethodik - Java Collections 25 Eigenschaften von Iteratoren • Ein Iterator läuft von Beginn an Element für Element durch eine Collection (keine Sprünge, kein Start an beliebiger Stelle). • Ein Iterator steht immer zwischen zwei Elementen (oder vor dem ersten, nach dem letzten). • Iterator ist verbraucht, wenn er am Ende angekommen ist (keine Wiederverwendung). • Für einen neuen Durchlauf muss ein neuer Iterator erzeugt werden. • Innerhalb einer Collection können gleichzeitig mehrere Iteratoren unterwegs sein. • Mehrere Iteratoren sind unabhängig voneinander und können einzeln bewegt werden. Programmiermethodik - Java Collections 26 Beispiel (ArrayList) … List<String> ls = new ArrayList<String>(); ls.add("A"); ls.add("B"); Muss übereinstimmen! ls.add("A"); ls.add("B"); … Iterator<String> is = ls.iterator(); while (is.hasNext()){ String s = is.next(); if (s.equals("A")) is.remove(); } System.out.println(ls); // [B B] … Programmiermethodik - Java Collections 27 Beispiel (Iterator, Map) import import import import java.util.HashMap; java.util.Iterator; java.util.Map; java.util.Set; public final class CollectionTest3 { private CollectionTest3() { } } public static void main(final String[] args) { final Map<String, Double> m = new HashMap<String, Double>(); m.put("PC1", 1199.90); Iterator über die Menge m.put("PC2", 1999.90); m.put("PC3", 980.00); der Einträge System.out.println(m); final Set<Map.Entry<String, Double>> mes = m.entrySet(); final Iterator<Map.Entry<String, Double>> imes = mes.iterator(); while (imes.hasNext()) { final Map.Entry<String, Double> item = imes.next(); if (item.getValue() > 1000) { System.out.println(item.getKey()); } } Ausgabe: } {PC3=980.0, PC1=1199.9, PC2=1999.9} Programmiermethodik - Java Collections PC1 PC2 28 Listen-Iteratoren • Für Listen gibt es noch eigene Iteratoren. Diese implementieren das Interface ListIterator<T>. • Ein Listen-Iterator kann sich vorwärts und rückwärts bewegen. • Zusätzlich zu den Methoden eines Iterators werden zum Beispiel angeboten: previous und hasPrevious, die sich auf das vorhergehende Element beziehen. previousIndex und nextIndex liefern die Indexwerte des vorhergehenden und nächsten Elements. • Ein Listen-Iterator ist nicht verbraucht, wenn er am Ende der Liste angekommen ist. Programmiermethodik - Java Collections 29 Modifikation und Iteratoren (1) • Wird eine Collection modifiziert (z.B. neues Element einfügen), dann werden alle Iteratoren ungültig. • Beim nächsten Zugriffsversuch auf einen Iterator nach einer Änderung wirft dieser eine ConcurrentModificationException. • Wird als fail-fast bezeichnet. Iterator gerät nicht irgendwann in einen inkonsistenten Zustand. Iterator wird sofort unbrauchbar gemacht. • Iteratoren reagieren nur auf strukturelle Änderungen, d.h. Ersetzen eines Elements verändert die Struktur nicht und lässt Iteratoren intakt. Programmiermethodik - Java Collections 30 Modifikation und Iteratoren (2) • Iteratoren bieten selbst Änderungsoperationen an, bei denen der betreffende Iterator funktionsfähig bleibt. • Alle Iteratoren definieren die Methode remove, die das zuletzt überquerte Element aus der Collection entfernt. • Aber Iterator muss sich zuerst bewegen, dann erst kann remove aufgerufen werden (löscht das letzte Element, das zurückgegeben wurde). Nach einem Aufruf muss erst wieder ein Element überquert werden. Bei Listen-Iteratoren ist das zuletzt überquerte Element abhängig von der Laufrichtung (kann vom Listenanfang aus gesehen vor oder hinter dem Iterator sein). Programmiermethodik - Java Collections 31 Modifikation und Iteratoren (3) • Bei Listen-Iteratoren gibt es neben remove noch zusätzliche Methoden. add – fügt das Element in der Lücke ein, in der der Iterator steht. set – ersetzt das zuletzt überquerte Element durch das übergebene Element. • Bei Änderungen über einen Iterator bleibt nur dieser eine Iterator intakt. • Andere Iteratoren, die in der gleichen Collection unterwegs sind, werden ungültig (bei set bleiben alle intakt – keine Strukturänderung). Programmiermethodik - Java Collections 32 foreach - Schleife • So wie Arrays können auch Collections mit der foreachSchleife durchlaufen werden. • Beispiel List<Integer> cl = new ArrayList<Integer>(); … for (Integer in : cl) System.out.println(in); • Die foreach-Schleife unterliegt den gleichen Beschränkungen wie bei Arrays. • foreach-Schleifen arbeiten wie Iteratoren. Eine Änderung der Collection während des Durchlaufens führt zu einer Ausnahme. Implementiert eine Klasse das Interface Iterable<T>, dann können Objekte dieser Klasse mit einer foreach-Schleife durchlaufen werden. Programmiermethodik - Java Collections 33 Wichtige Themen Abstrakte Collection-Klassen • Unterstützen die Entwicklung neuer Collection-Klassen. • Abstrakte Klassen implementieren schon einen großen Teil eines Interfaces und lassen bestimmte Teile noch offen. • Eine neue Collection-Klasse kann daher schon auf eine abstrakte Klasse aufbauen (muss nicht alle Methoden eines Interfaces implementieren). • Vorgehensweise bei der Implementierung einer neuen Collection-Klasse: Auswählen einer geeigneten abstrakten Klasse von der geerbt wird. 2. Implementierung aller abstrakten Methoden. 3. Sollte die Collection modifizierbar sein, dann müssen auch einige konkrete Methoden überschrieben werden (wird in der APIDokumentation der abstrakten Klasse beschrieben). 4. Testen der neuen Klasse (inklusive Performance). Programmiermethodik - Java Collections 35 1. Beispiele für abstrakte Klassen • Beispiele AbstractCollection AbstractSet AbstractList (basierend auf Array) AbstractSequentialList (basierend auf verketteter Liste) AbstractQueue AbstractMap • In der API-Dokumentation jeder abstrakten Klasse wird genau beschrieben, wie man eine Klasse davon ableiten muss. Grundlegende Implementierung Welche Methoden müssen implementiert werden. Welche Methoden müssen überschrieben werden, wenn man Modifikationen zulassen möchte. Programmiermethodik - Java Collections 36 Algorithmen im Collection-Framework • Das Collection-Framework bietet neben vielen vordefinierten Containerklassen auch Algorithmen für die Verarbeitung von Container-Klassen an. • Diese Algorithmen werden als statische Methoden (polymorphe Methoden) in der Hilfsklasse Collections gesammelt. • Algorithmen (Beispiele) sort binarySearch max shuffle Sortiert die Elemente einer generischen Liste nach aufsteigender Größe. Sucht ein Element in der sortierten Liste (Voraussetzung) und liefert einen Index zurück, wenn das Element gefunden wurde (ansonsten eine negative Zahl). Liefert das größte Element einer Collection. Mischt die Elemente einer generischen Liste zufällig. Programmiermethodik - Java Collections 37 Vergleiche • Viele Methoden (z.B. sort, binarySearch) vergleichen Elemente der Größe nach. • Für beliebige Elementtypen ist aber nicht ohne weiters klar, welches von zwei Objekten das „größere“ ist. Klassen müssen das generische Interface Comparable<T> implementieren. Es muss die Vergleichsmethode compareTo implementiert werden, mit dem Ergebnis > 0 wenn das Objekt größer als der Parameter ist, < 0 wenn das Objekt kleiner als der Parameter ist, 0 wenn das Objekt und der Parameter gleich sind. Programmiermethodik - Java Collections 38 Comparator • Oft sollen Objekte nach verschiedenen Kriterien verglichen werden. • Daher sind viele Collection-Methoden mit einem zusätzlichen Parameter, einem Comparator, überladen. Erfüllt den gleichen Zweck. Zum Vergleich wird aber das übergebene Comparator-Objekt benutzt. • Comparator<T> ist ein generisches Interface mit einer Methode compare. Methode akzeptiert zwei Objekte des Elementtyps und liefert ein intErgebnis für den Vergleich der zwei Objekte. Programmiermethodik - Java Collections 39 Comparator-Beispiel List<String> ss = new ArrayList<String>(); ss.add("Hans"); ss.add("Sepp"); ss.add("franz"); System.out.println(ss); // [Hans, Sepp, franz] Collections.sort(ss); System.out.println(ss); // [Hans, Sepp, franz] Comparator<String> ignoreCaseComparator = new Comparator<String>() { public int compare(String s1, String s2) { return (s1.toLowerCase().compareTo(s2.toLowerCase())); } }; Collections.sort(ss, ignoreCaseComparator); System.out.println(ss); //[franz, Hans, Sepp] Programmiermethodik - Java Collections 40 Unveränderliche Collection-Klassen • In vielen Fällen möchte man Container-Klassen zur Verfügung stellen, die unveränderlich sind. • Die Klasse Collections bietet einige statische Methoden, mit denen unveränderliche Sichten auf vorhandene Container erzeugt werden können. Sichten auf eine Datenstruktur kopieren eine zugrunde liegende Datenstruktur nicht. Bieten eine eingeschränkte Darstellung (z.B. Sperren aller schreibenden Methoden). Die Datenstruktur wird nicht verändert. Programmiermethodik - Java Collections 41 Beispiel • Methoden (Beispiele) public static <T> List<T> unmodifiableList(List<? extends T> list) public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) public static <T> Set<T> unmodifiableSet(Set<? extends T> s) • Anwendung … HashMap<String,Integer> m = new HashMap<String,Integer>(); Map<String,Integer> rom = Collections.unmodifiableMap(m); m.put("TEST",1); // rom.put("TEST",1);FEHLER! … Programmiermethodik - Java Collections 42 Übersicht (wichtige Klassen) Typ Ordnung bleibt Null-Elemente Duplikate ArrayList ja ja ja LinkedList ja ja ja HashSet nein ja nein TreeSet ja nein nein HashMap nein ja Schlüssel nein, Werte ja TreeMap ja Schlüssel nein, Werte ja Schlüssel nein, Werte ja Programmiermethodik - Java Collections 43