Collections aus dem Baukasten

Werbung
Java Core High-Performance Lists – Teil 2
Key Collections: Ein Baukasten für Java Collections
Collections aus dem
Baukasten
Jede Applikation benötigt Daten. Diese werden mit dem (Java) Collections Framework verwaltet, das
deshalb zu den wichtigsten Teilen des JDK gehört. Es definiert Interfaces und stellt Klassen mit Standardimplementierungen bereit. Allerdings kommt es häufig vor, dass man keine Klasse findet, die
genau den Anforderungen entspricht. Entweder akzeptiert man diese Lücke, schließt sie mit Programmieraufwand oder zieht Collections aus zusätzlichen Bibliotheken hinzu. Viel besser wäre es doch,
wenn man die benötigten Collections selbst wie aus einem Baukasten zusammenstellen könnte. Genau diese Funktionalität stellt die Brownies Collections Library mit den Key Collections zur Verfügung.
von Thomas Mauch
Der Artikel „High-Performance Lists für Java“ in der
letzten Ausgabe des Java Magazins hat mit GapList und
BigList Alternativen zu den List-Implementierungen des
JDK vorgestellt, die in allen Fällen schnell sind und gut
skalieren. In diesem Artikel stellen wir mit den Key Col­
lections den zweiten Hauptteil der Brownies Collections
Library [1] vor. Sie erlauben es, Collections mit konfigurierbarer Funktionalität zur Laufzeit zu erzeugen.
Bereits im ersten Artikel haben wir das spartanische
API des List-Interface bemängelt. Es gibt aber noch
weitere Kritikpunkte am Java-Collections-Framework:
So decken die zur Verfügung gestellten Interfaces und
Klassen nur die einfachsten Anwendungsfälle ab. Neben dem List-Interface gibt es nur noch Set und Queue/
Deque, die ebenfalls von Collection erben, und getrennt von dieser Hierarchie noch Map – komplexere
Datenstrukturen bleiben außen vor. Wenn man solche
verwenden will, muss man sie entweder selbst aus den
bestehenden zusammenbauen oder zusätzliche Libraries
wie z. B. Googles Guava [2] einsetzen. Guava fügt dann
beispielsweise die Interfaces Multiset, Multimap und BiMap mit entsprechenden Implementierungen hinzu, die
weitere häufige Anwendungsfälle abdecken.
Allerdings wird man so mit einer Vielzahl von Interfaces und Klassen konfrontiert, die neu, unterschiedlich
und zu erlernen sind, bevor die volle Funktionalität
genutzt werden kann. Und da sich die Klassen typi-
Artikelserie
Teil 1: Brownies Collections: GapList und BigList
Teil 2: Key Collections
12
javamagazin 5 | 2015
scherweise nicht konfigurieren lassen, findet man dann
vielleicht doch wieder bloß eine Klasse, die den gewünschten Anwendungsfall nur fast, aber eben nicht
ganz abdeckt. Viel besser wäre es doch, wenn man die
benötigten Collections selbst wie aus einem Baukasten
zusammenstellen könnte. Genau diese Funktionalität
stellt die Brownies Collections Library mit den Key
Collections zur Verfügung. Die Vielfältigkeit wird durch
die Integration der Konzepte von Keys und Constraints
ermöglicht, die durch ihre Mächtigkeit die Produktivität
beim Entwickeln um ein Vielfaches erhöhen kann.
Erstes Beispiel
Am einfachsten wird die Nützlichkeit der Funktionalität mit einem Beispiel erklärt: Wir wollen in unserem
Programm die Metainformationen zu den Spalten einer Datenbanktabelle repräsentieren, ähnlich wie sie
beispielsweise java.sql.ResultSetMetaData bereitstellt,
aber es sollen ebenfalls Änderungen unterstützt werden.
Damit ergeben sich folgende Anforderungen:
•Spalten haben eine explizite Ordnung, wie sie z. B.
für eine Abfrage mit select * verwendet wird.
•Die Spaltennamen einer Tabelle müssen einzigartig
sein, doppelte Namen müssen also verhindert werden.
•Effizienter Zugriff auf die Spalteninformationen soll
sowohl über die Position der Spalte in der Tabelle
als auch über den Spaltennamen möglich sein, analog wie java.sql.ResultSet den Zugriff auf Daten mit
getObject(int) und getObject(String) erlaubt.
Wenn wir überlegen, welche Collection wir für diese
Aufgabe wählen sollen, stellen wir fest, dass uns das
JDK keine optimale Lösung bietet. Eine List erlaubt nur
effizienten Zugriff über die Position, nicht aber über den
www.JAXenter.de
High-Performance Lists – Teil 2 Java Core
Namen, während es bei einer Map gerade umgekehrt
ist. Nur wenn man davon ausgehen kann, dass die Liste
immer wenig Elemente enthält, können wir den Zugriff
über den Namen durch Iteration realisieren. Eine Tabelle wird zwar nie eine wirklich riesige Menge von Spalten
haben, aber das Iterieren wird bereits bei tausend Einträgen nicht mehr wirklich effizient sein. Um eine wirklich skalierbare Lösung zu erhalten, müssen wir die List
deshalb mit einer Map synchronisieren. Dies ist keine
unmögliche Aufgabe, aber doch jede Menge Arbeit für
eine eigentlich alltägliche Anforderung. Mithilfe der Key
Collections hingegen können wir eine Datenstruktur mit
den gewünschten Eigenschaften mit einem einzigen Aufruf erzeugen (Listing 1).
Um die gewünschte Funktionalität zu erreichen, nutzt
das Beispiel sowohl Keys als auch Constraints. Diese
beiden Konzepte wollen wir nun vorstellen.
Constraints
Ein Constraint auf einer Collection definiert, welche Bedingungen Elemente erfüllen müssen, damit sie in der
Collection enthalten sein können. Beispiele für Con­
straints sind also, dass Elemente nicht null sein oder
dass gespeicherte Strings nur Großbuchstaben enthalten
dürfen. Zwar haben auch gewisse JDK-Klassen implizite
Constraints, z. B., dass ein Set keine Duplikate enthalten
kann oder dass ArrayDeque keine Nullwerte erlaubt,
aber diese Einschränkungen sind fix und können nicht
nach Bedarf geändert werden.
Will man konfigurierbare Constraints mit den JDKKlassen realisieren, stellt man schnell fest, dass die
Klassen nicht für solche Erweiterbarkeit entwickelt
wurden: Um die erlaubten Elemente von ArrayDeque
zu beschränken, müsste man z. B. alle Methoden, die
das Hinzufügen von Elementen erlauben (add/addLast/
addFirst, offer/offerFirst/offerLast, addAll), einzeln
überschreiben, und kann dies nicht an einem zentralen
Ort tun.
Weshalb sind aber Constraints so wichtig? Con­
straints sind nicht nur zur Datenhaltung selbst wichtig,
sondern vor allem ein zentraler Baustein, um ein mächtiges API zur Verfügung stellen zu können. Gehen wir
wieder zurück zu unserem Beispiel mit den Datenbankspalten und betrachten die Beispielklasse Table:
class Table { List<Column> columns; }
Welches API stellen wir den Nutzern der Klasse zur Verfügung, um die Spaltendefinition anlegen oder ändern
zu können? Klar ist, dass die einfache Methode
List<Column> getColumns { return columns; }
gefährlich ist, da wir damit nicht garantieren können,
dass die auf der zurückgegebenen Liste durchgeführten
Änderungen die definierten Constraints erfüllen. Mit einer leicht angepassten Methode können wir zumindest
den Lesezugriff realisieren:
www.JAXenter.de
List<Column> getColumns { return Collection.unmodifiableList(columns); }
Das API für Änderungen fehlt uns aber noch immer.
Dazu bleiben zwei Möglichkeiten: eine ineffiziente für
den faulen Programmierer, die nur eine einzige Methode zur Verfügung stellt:
void setColumns(List<Column> columns) {
check(columns); this.columns = new ArrayList(columns);
}
Das Problem mit diesem Ansatz ist, dass wir bei jedem
Aufruf die Constraints für alle Elemente wieder prüfen müssen, auch wenn vielleicht nur eine einzige Änderung gemacht wurde. Dieser Ansatz skaliert damit
nicht wirklich gut.
Damit bleibt nur noch die andere, aufwändigere
Möglichkeit: Wir stellen für jede Art von Änderung, die
durchgeführt werden können muss, eine separate Methode zur Verfügung, wie z. B.:
void addColumn(Column column) {
check(column); columns.add(columns);
}
Für ein vollständiges API brauchen wir dann aber auch
noch die Methoden setColumn, removeColumn, vielleicht
noch addColumn an einer angegebenen Position, und
auch das Löschen von allen Spalteninformationen sollte
möglich sein. Wie man sieht, artet das einfache API schnell
in Fleißarbeit aus – oder aber das API bleibt unvollständig
und ist damit unhandlich zu bedienen. Aber auch wenn
diese Arbeit machbar ist, sollte man sich doch wieder die
Frage stellen, ob es dafür keine einfachere Lösung gibt.
Tatsächlich können wir alle Anforderungen durch
Einsatz der Key Collections mit der einfachstmöglichen
Implementierung realisieren, da dann die Collection
selbst sicherstellt, dass alle gespeicherten Elemente die
Constraints jederzeit erfüllen:
Listing 1
// Definition of type Column
class Column {
String name;
String type;
// Add constructor / getters / setters
}
// Create list with columns
Key1List<Column,String> cols = new Key1List.Builder<Column,String>.
withKey1Map(Column::getName).withKey1Null(false).withKey1Duplicates
(false).build();
// Populate and query list
cols.add(new Column("name", "varchar"));
Column col = cols1.getByKey1("name");
javamagazin 5 | 2015
13
Java Core High-Performance Lists – Teil 2
class Table {
Key1List<Column,String> columns = ...;
List<Column> getColumns { return columns; }
}
Keys
Ein Key ist ein Wert, der von einer Funktion aus einem
in einer Collection gespeicherten Element bestimmt wird.
Wie man in Listing 1 sehen kann, speichern die Key Col­
lections deshalb immer Elemente – dies als Gegensatz zum
JDK-Map-Interface, das Elemente mit externen Keys assoziiert. Der Nachteil der JDK-Entscheidung ist, dass Map
deshalb komplett getrennt von allen anderen Interfaces ist
und nicht einmal das Basisinterface Collection erweitert.
Schaut man sich dann aber Code an, der mit Maps arbeitet, stammt der Key in der Mehrzahl der Fälle aus dem
Element selbst, d. h., die Map wird mit Aufrufen wie
map.put(elem.getName(), elem);
befüllt. So gesehen kann man sagen, dass sich die Key
Collections auf den Normalfall konzentrieren, wenn sie
grundsätzlich nur die Elemente selbst speichern und die
Keys bei Bedarf mit Funktionen aus den gespeicherten
Elementen bestimmen. Diese Entscheidung stellt auch
keine wirkliche Einschränkung dar, denn sollte der Key
einmal nicht in der Element-Klasse selbst vorhanden
sein, kann dies durch die Definition einer Hilfsklasse
einfach aufgefangen werden:
class Entry { String key; Column column; }
Wir bezeichnen die von der Funktion definierten Werte
als Key Map. Wie auch Einträge in einer Map oder einem
Set sollten die als Keys genutzten Werte grundsätzlich immutable sein, d. h., nach dem Hinzufügen des Elements
zur Collection dürfen sich diese nicht mehr ändern.
Während Listing 1 eine Collection mit einem definierten Key zeigt, kann es pro Collection auch keine oder
zwei Key Maps geben. Auch das Element selbst kann
als Key genutzt werden; in diesem Fall sprechen wir von
einem Element Set.
Das Beispiel zeigt auch, dass die Keys einer Collection nicht nur für den effizienten Zugriff auf die Elemente, sondern auch wieder für die Definition von
Constraints verwendet werden können. Für jede Key
Map oder das Element Set können folgende Eigenschaften festgelegt werden:
Tabelle 1:
Übersicht
über die
KeyCollectionsKlassen
Mit Kenntnissen relationaler Datenbanken werden einem diese Konzepte bekannt vorkommen. So haben
wir in unserem Beispiel eigentlich den Spaltennamen als
Primary Key der Collection definiert. Und analog zum
Primary Key in einer Datenbanktabelle garantiert der
definierte Constraint, dass die zu speichernden Elemente
korrekt sind und erlaubt einen effizienten Zugriff über
den Constraint Key. Auch andere Konzepte aus der Datenbankwelt, wie Check Constraints oder Triggers, die
für das manuelle Prüfen von Elementen nützlich sein
können, haben ihre Entsprechung in den Key Col­lec­
tions.
Beispiele
Doch nun genug der Theorie. Anhand von Beispielen
wollen wir nun die Möglichkeiten aufzeigen, die sich
aus den besprochenen Konzepten ergeben.
Die Key Collections implementieren das Collectionoder das List-Interface. Durch die Kombination mit Anzahl der genutzten Keys erhalten wir sechs Klassen, die
Tabelle 1 im Überblick zeigt.
Um die Vielzahl der Optionen vernünftig unterstützen
zu können, geschieht das Anlegen von Key Collections
immer über ein Builder-Pattern. Das zeigt unser erstes Beispiel, das eine einfache Integer-Liste ohne Con­
straints und Keys erzeugt:
KeyList<Integer> list = new KeyList.Builder<Integer>().build()
Die erzeugte KeyList verhält sich im Wesentlichen wie
eine GapList und bietet über das IList-Interface auch
alle bekannten Methoden an. Im Gegensatz zu einer
GapList kann die KeyList nun aber beim Erstellen nach
Belieben konfiguriert werden, indem wir z. B. die erlaubten Elemente mit einem Constraint einschränken:
Klasse
Beschreibung
KeyCollection<E>
Key1Collection<E,K1>
Die Klassen KeyCollection, Key1Collection und Key2Collection implementieren
das Collection-Interface. Die Reihenfolge der Elemente ist per Default nicht definiert, sie kann aber von einer der Key Maps bestimmt werden.
Key2Collection<E,K1,K2>
KeyList<E>
Key1List<E,K1>
Key2List<E,K1,K2>
14
•Nullwerte: Erlaubt oder verboten.
•Duplikate: Erlaubt oder verboten. Ebenfalls kann
spezifiziert werden, dass zwar Nullwerte mehrmals
vorkommen können, andere Werte aber einzigartig
sein müssen.
•Sortierung: Sortiert oder nicht. Beim Sortieren kann
entweder die natürliche Ordnung der Klasse verwendet oder explizit ein eigener Comparator angegeben
werden. Wenn eine Key Map sortiert ist, kann ebenfalls festgelegt werden, dass diese Reihenfolge auch
für die Elemente selbst verwendet werden soll, wodurch wir eine sortierte Collection erhalten.
javamagazin 5 | 2015
Die Klassen KeyList, Key1List und Key2List implementieren das List-Interface,
d. h., die Reihenfolge der Elemente wird immer durch die Liste bestimmt. Es
kann aber festgelegt werden, dass die Reihenfolge der Liste der Reihenfolge
einer Key Map entspricht, wodurch eine sortierte Liste entsteht.
www.JAXenter.de
Anzeige
Java Core High-Performance Lists – Teil 2
•Eine Integer-Liste, die keine Nullwerte zulässt:
new KeyList.Builder<Integer>().withElemNull(false).build()
•Eine Integer-Liste, die keine Nullwerte und nur positive Zahlen zulässt:
new KeyList.Builder<Integer>().withElemNull(false).withConstraint(i -> i>=0).
build()
Wenn nun versucht wird, ein Element hinzuzufügen, das
die definierten Bedingungen nicht erfüllt, wird eine Exception geworfen. Neben den Elementen selbst kann auch die
Anzahl der erlaubten Elemente eingeschränkt werden:
•Eine Liste mit einer fixen maximalen Anzahl von
Elementen, d. h. das Hinzufügen eines Elements
wird fehlschlagen, wenn die Liste bereits voll ist:
new KeyList.Builder<Integer>().withMaxSize(10).build()
•Eine Liste mit einer maximalen Anzahl von Elementen, die als rollendes Fenster organisiert sind, d. h.
wenn die Liste voll ist, wird das Hinzufügen eines
neuen Elements automatisch das erste Element aus
der Liste entfernen, damit die festgelegte Größe nicht
überschritten wird:
Alle offerierte Funktionalität ist bisher alleine auf der
zugrunde liegenden Listenstruktur aufgebaut. So kann
zwar die Standardfunktion contains verwendet werden,
die Implementierung ist aber langsam, da zur Ausführung über alle Elemente der Liste iteriert werden muss.
Diese Operation kann beschleunigt werden, indem die
KeyList angewiesen wird, neben der eigentlichen Liste
auch noch ein Element Set zu führen – eine Integer-Liste
mit einem Element Set für schnelle Zugriffe:
new KeyList.Builder<Integer>().withElemSet().build()
Damit sind nun auch Operationen wie contains oder remove sehr schnell, da diese über eine intern angelegte
Map ausgeführt werden. Als einzige Einschränkung ist
das Entfernen von Elementen über den Key bei Listen
langsam, da das Element in der Liste durch Iterieren gesucht werden muss. In größeren Listen sollten deshalb
Elemente immer über den Index gelöscht werden – oder
man verwendet sortierte Listen. Die Map erlaubt auch
eine effiziente Prüfung auf Duplikate – eine Integer-Liste, die keine Duplikate zulässt:
new KeyList.Builder<Integer>().withWindowSize(10).build()
new KeyList.Builder<Integer>().withElemDuplicates(false).build()
Wenn komplexere Bedingungen geprüft werden müssen,
die nicht nur vom Element selbst oder von den sich bereits
in der Collection befindlichen Elementen abhängt, kann
dies analog der Datenbankwelt mit Triggern gelöst werden.
•Eine Liste, die vor dem Einfügen eines Elements eine
Trigger-Funktion aufruft. Diese Funktion wird als
Consumer-Interface definiert und so dem Trigger
übergeben:
new KeyList.Builder<Integer>().withBeforeInsert(...).build()
Die Funktionen withBeforeInsert und withBeforeDelete
erlauben das explizite Prüfen von Elementen, bevor eine
Änderung durchgeführt wird. Wird in der angegebenen
Funktion eine Exception geworfen, wird die Operation
abgebrochen. Die Funktionen withAfterInsert und with­
AfterDelete werden entsprechend nach dem Durchführen der Operation aufgerufen.
Ebenfalls kann die zur Speicherung der Elemente verwendete Datenstruktur angepasst werden, sodass Speicherverbrauch und Performance für jeden Fall optimiert
werden können:
•Integer-Werte werden in einer GapList gespeichert:
new KeyList.Builder<Integer>().build()
•Integer-Werte werden in einer BigList gespeichert:
new KeyList.Builder<Integer>().withElemBig(true).build()
•Integer-Werte werden in einer IntObjGapList gespeichert:
new KeyList.Builder<Integer>().withElemClass(int.class).build()
•Integer-Werte werden in einer IntObjBigList gespeichert:
new KeyList.Builder<Integer>().withElemBig(true).withElemClass(int.class).
build()
16
javamagazin 5 | 2015
Während die Key Maps normalerweise nicht sortiert
sind, kann auch dies angepasst werden – eine IntegerListe mit einer sortierten Key Map:
new KeyList.Builder<Integer>().withElemSort(true).build()
Durch das Sortieren des Element Set wird die Reihenfolge der Liste als solche noch nicht beeinflusst. Man kann
die Collection aber so konfigurieren, dass eine sortierte
Liste entsteht – eine sortierte Integer-Liste:
new KeyList.Builder<Integer>().withElemSort(true).withElemOrderBy(true).build()
Zu sortierten Lists gibt es zahlreiche Diskussionen [3],
weshalb Java diese nicht kennt. Es gibt zwei Hauptargumente, die dagegen sprechen: Das erste, puristische
Argument ist, dass die add-Funktion den Contract des
List-Interface verletzt, da das Element nicht am Ende
der Liste hinzugefügt wird. Das zweite, praktische
Argument ist, dass es nicht möglich ist, eine sortierte
Liste zu implementieren, in der Elemente in zufälliger
Reihenfolge effizient hinzugefügt werden können (da in
diesem Fall immer Elemente verschoben werden müssen, wodurch die Performance leidet). Während beide
Argumente eine gewisse Berechtigung haben, kann eine
sortierte Liste dennoch nützlich und – wenn sie richtig
genutzt wird – auch effizient sein.
Nun erweitern wir das Beispiel um die Definition eines Keys, wie wir ihn bereits in Listing 1 gesehen haben
– eine Liste mit Spaltenelementen und Zugriff über den
Spaltennamen:
new Key1List.Builder<Column,String>.withKey1Map(Column::getName).build()
www.JAXenter.de
High-Performance Lists – Teil 2 Java Core
Für die definierten Keys stehen im Wesentlichen dieselben Möglichkeiten zur Verfügung wie für das Element
Set. So definiert z. B. die Methode withKey1Null einen
Constraint auf dem definierten Key, wie withElemNull
einen Constraint auf dem Element selbst definiert hat
– eine Liste mit Spaltenelementen mit einzigartigen Namen und Zugriff über den Spaltennamen:
new Key1List.Builder<Column,String>.withKey1Map(Column::getName).
withKey1Null(false).withKey1Duplicates(false).build()
Da das Definieren eines solchen Primary Keys eine häufige Anforderung ist, gibt es als Shortcut die Methoden
withPrimaryKey1 und withUniqueKey1, die Nullwerte
und Duplikate in einem Aufruf definieren. Damit vereinfacht sich auch die Definition der Liste – eine Liste
mit Spaltenelementen mit einzigartigen Namen und
Zugriff über den Spaltennamen:
new Key1List.Builder<Column,String>().withKey1Map(Column::getName).
withPrimaryKey1().build()
Während wir bisher nur die Funktionen zum Definieren
der Key Collections untersucht haben, stellen wir nun
noch die Funktionen zum Arbeiten mit den erstellten
Datenstrukturen vor.
Für den Zugriff auf das Element Set können neben der
Standardfunktion contains die neuen Methoden getAll,
getCount, getDistinct und removeAll genutzt werden.
Für das Arbeiten mit Key Maps gibt es die Methoden
containsKey, getByKey, getAllByKey, getCountByKey,
getDistinctKeys, removeByKey, removeAllByKey und
indexOfKey (nur für Listen).
Ebenfalls kann mithilfe der Methoden asSet und asMap über die JDK-Standardinterfaces auf Element Set
und Key Maps zugegriffen werden.
Dadurch, dass sich die aufgezeigten Möglichkeiten beliebig kombinieren lassen, kann man mit den
Key Collections maßgeschneiderte Collections für alle
Anwendungsfälle erzeugen. Listing 2 enthält weitere
Beispiele. Unter anderem wird gezeigt, wie man mit wenigen Codezeilen eine BiMap erstellen kann, also eine
Map, wo man sowohl effizient über den Key auf den
Wert als auch umgekehrt über den Wert auf den Key
zugreifen kann. Weitere Beispiele finden sich auch in der
Dokumentation der Library [4].
Es gibt einen Spezialfall, der nicht orthogonal auf alle
Key Collections angewendet werden kann, sondern nur
von KeyCollection unterstützt wird. Es handelt sich
dabei um die Möglichkeit, ein Multiset zu realisieren,
in dem identische Elemente nur einmal zusammen mit
einem Zähler für ihr Vorkommen gespeichert werden
Anzeige
Anzeige
Java Core High-Performance Lists – Teil 2
schiedlichen Collection-Klassen zusammengestellt wird,
die die gewünschte Funktionalität effizient implementieren. Key Maps und Element Set werden typischerweise
mit HashMap/TreeMap realisiert, bei sortierten Listen
wird auch die Key Map, die die Sortierreihenfolge vorgibt, als List gespeichert. Die Elemente der Liste selbst
werden per Default in einer GapList gespeichert, bei
Bedarf kann aber, wie gesehen, auch eine BigList oder
eine primitive Wrapper-Klasse wie IntObjGapList oder
IntObjBigList verwendet werden.
Abb. 1:
Datenspeicherung in
„Key1List“
(unsortiert und
sortiert)
Fazit
Die Key Collections erweitern die Möglichkeiten der
Java Collections auf eine faszinierende Art und Weise.
Die einfache Definition von mächtigen Datenstrukturen, denen die gewünschte Funktionalität deklarativ zur
Laufzeit zugewiesen werden kann, erlaubt ein präzises
Abbilden des Datenmodells.
Die Brownies Collections Library erhöht damit die
Effizienz beim Entwickeln und ermöglicht dem Entwickler mit geringem Programmieraufwand, Applikationen zu erstellen, die in jeder Situation effizient
bezüglich Performance und Speicherplatzverbrauch
sind und dadurch gut skalieren.
– ein Multiset von Strings, in dem für jeden String die
Häufigkeit gespeichert wird:
new KeyCollection.Builder<String>.withElemCount().build()
Thomas Mauch arbeitet als Softwarearchitekt bei Swisslog in
Buchs, Schweiz. Seine Schwerpunkte liegen im Bereich Java und
Datenbanken. Er interessiert sich seit den Zeiten des C64 für alle
Aspekte der Softwareentwicklung.
Implementierung
Wir wollen auch noch einen Blick hinter die Kulissen
werfen und sehen, wie die Key Collections die vorgestellte Funktionalität implementieren. Vielleicht erinnern wir
uns noch daran, wie wir bei der Vorstellung des ersten
Beispiels erwähnt haben, dass man für eine skalierende
Lösung eine List mit einer Map synchronisieren muss –
genau das tun die Key Collections bei Bedarf für uns.
Abbildung 1 zeigt, wie eine Key1List je nach Anforderung, ob die Liste sortiert sein soll oder nicht, aus unter-
Links & Literatur
[1] http://www.magicwerk.org/collections
[2] https://code.google.com/p/guava-libraries/
[3] http://stackoverflow.com/questions/8725387/why-there-is-nosortedlist-in-java
[4] http://www.magicwerk.org/page-collections-examples.html
Listing 2
// Eine nach Namen sortierte File-List mit Namen als
// Primary Key
Key1List<File,String> coll1 =
new Key1List.Builder<File,String>().
withKey1Map(File::getName).
withPrimaryKey1().withKey1OrderBy(true).build();
// Eine Collection mit 2 Keys, wobei 1 Key optional
// ist
Key2Collection<Ticket,String,String> coll2 =
new Key2Collection.Builder<Ticket,String,String>().
withKey1Map(Ticket::getId).withPrimaryKey1().
withKey2Map(Ticket::getExtId).withUniqueKey2().
build();
18
javamagazin 5 | 2015
// Eine BiMap, welche Zip-Codes verwaltet, wobei
// alle Key Maps sortiert sind
class Zip {
int code;
String city;
Zip(int code, String city) {
this.code = code;
this.city = city;
}
int getCode() {
return code;
}
String getCity() {
return city;
}
}
void useBiMap() {
Key2Collection<Zip, Integer, String> zips =
new Key2Collection.Builder<Zip, Integer,
String>().
withKey1Map(Zip::getCode).withKey1Sort(true).
withKey2Map(Zip::getCity).withKey2Sort(true).
build();
zips.add(new Zip(1000, “city1000”));
String city = zips.getByKey1(1000).getCity();
int code = zips.getByKey2(“city1000”).getCode();
}
www.JAXenter.de
Jetzt abonnieren und
3 TOP-VORTEILE sichern!
1
Alle Printausgaben
frei Haus erhalten
2
Im entwickler.kiosk
immer und überall
online lesen – am
Desktop und mobil
3
Mit vergünstigtem
Upgrade auf das
gesamte Angebot
im entwickler.kiosk
zugreifen
Java-Magazin-Abonnement abschließen auf www.entwickler.de
www.entwickler.de
Herunterladen