http://www.youtube.com/user/AlgoRythmics. Das ist ja cool. Eine super Idee!
Autor: Christian Ullenboom
Was ist JavaFX?
AWT und Swing sind bisher die Standardlösungen für grafische Anwendungen unter Java. AWT bildet das Fundament mit Ereignisbehandlung, Fenster-Management und einer mächtigen 2D-API. Swing sitzt auf dem AWT und ist eng mit ihm verbunden. Es realisiert die Komponenten, die zum Teil selbst in Java implementiert sind, manch ein Look and Feel – wie im Fall von Windows – lässt die Komponenten nativ vom Betriebssystem zeichnen.
Swing und AWT sind mächtig, aber es hat sich in den Letzen Jahren nicht großartig weiterentwickelt. Insbesondere gibt es Lücken im Bereich Medien und Animation, etwas, was bei modernen grafischen Oberflächen heutzutage gefragt ist. Statt das Sun/Oracle in die Weiterentwicklung investiert, hat sich das Unternehmen für eine komplette Neuentwicklung der GUI-Ebene entschieden, die nichts mehr mit Swing/AWT gemeinsam hat: JavaFX.
JavaFX ist eine Komplettlösung mit einer API für
· GUI-Komponenten
· HMTL/CSS/JavaScript mit eingebetteten Web-Brower
· Animationen
· Video
· Audio
· 2D und 3D
Da JavaFX komplett alle APIs für moderne Oberflächen anbietet, und auch nicht von AWT/Swing abhängig ist, bildet JavaFX einen kompletten Media-Stack. Die Betonung liegt auf Media, denn die AWT/Swing-API im Java SE kann keine Medien einbinden oder abspielen. Zwar ist JavaFX auch noch kein Teil der Java SE, doch das kann sich ändern. Über Profile sollte JavaFX auch auf mobilen Endgeräten und im Internet wie Applets laufen, allerdings ist eher davon auszugehen, das JavaFX es bei Rich-Client-Anwendungen Einzug hält und dort AWT/Swing verdrängt. Es ist nicht abzusehen, dass JavaFX im Internet als Flash-Ersatz oder auf mobilen Endgeräten punkten kann, dafür ist die Kombination HTML5 + CSS3 + JavaScript zu attraktiv.
Anders als AWT ist die JavaFX-Implementierung auf der Höhe der Zeit und greift direkt auf alle 2D/3D-Fähigkeiten moderner Grafikkarten zurück. So kann mit JavaFX alles das programmiert werden, was bisher eher mit Flash gemacht wurde, wohl aber fehlen noch die tollen Entwicklertools. Es gibt Plugins für Adobe Photoshop und Illustrator, mit denen Grafiken und Pfade exportiert werden können, aber eben keine ganzen Animationen, die etwa mit Adobe Flash erzeugt wurden. Und seit dem Adobe Flash auch HTML5 exportiert, öffnet sich eine ganz neue Welt.
Geschichte JavaFX ist schon sehr lange in Entwicklung und viele interne Swing-Entwickler wurden auf das Projekt angesetzt – daran liegt es wohl auch, dass bei Swing nicht mehr passierte. Im Jahr 2003 wurde JavaFX dann auf der SunOne Konferenz vorgestellt, zusammen mit der Programmiersprache Java FX Script. Die Sprache macht es einfach möglich hierarchische Objektgrafen aufzubauen, und bot eine nette Syntax für Objekt-Bindung, doch wurde sie für die aktuelle Version JavaFX 2.0 fallen gelassen. Oracle wollte keine weitere Programmiersprache, sondern eine pure Java API, die sich von unterschiedlichen existierenden Skriptsprachen dann ansprechen kann. Das ist sicherlich eine gute Entscheidung, denn unter Groovy sieht das sehr schlank aus, fast wie mit JavaFX Script auch (http://groovy.codehaus.org/GroovyFX). |
Microsoft Office-Dokumente in Java verarbeiten
Auch wenn sich die Welt nach freien und quelloffenen Lösungen sehnt, Microsoft Office ist immer noch eines der bestverkauftesten Produkte auf diesen Planeten mit dem MS-Dokumentenformat als Sonne im Mittepunkt. Da die Dokumentation von Microsofts Dateiformaten mittlerweile zugänglich[1] sind, gibt es auch Java-Bibliotheken, die MS-Office-Dokumente einlesen, modifizieren und schreiben. Bekannt dafür ist Apache POI[2] (http://poi.apache.org/), was APIs für folgende Formate (Komponenten genannt) bietet:
POI-Komponete |
Aufgabe |
Grad der Unterstützung |
HSSF, XSSF |
Excel XLS, Excel XLSX |
Gut |
HSLF, XSLF |
PowerPoint PPT, PowerPoint PPTX |
Ausreichend |
HWPF, XWPF |
Word DOC, Word DOCX |
Befriedigend |
HDGF |
Visio VSD |
Rudimentär, nur lesen |
HPBF |
Publisher PUB |
Rudimentär, nur lesen |
HMEF/HSMF |
Outlook MSG/ Microsoft TNEF (Transport Neutral Encoding Format) |
Rudimentär, nur lesen |
OpenXML4J |
Open Packaging Conventions (OPC)/OOXML |
Gut |
POIFS |
OLE 2 Compound Document (OLE2 Filesystem) |
Sehr gut |
HPSF |
OLE2 Property Sets |
Sehr gut |
POI-Komponenten nach http://poi.apache.org/overview.html#components
OpenXML4J und POIFS sind keine üblichen Dokumentenformate, aber Archivformate für Microsoft-Dokumenten (analog zu Zip). HPSF erlaubt Zugriff auf Datei-Metadaten wie Autor, Titel, usw.
Neben POI gibt es nicht mehr so viele Alternativen, eher kleinere Bibliotheken, die sich auf ein Formate spezialisiert haben. So etwa http://jexcelapi.sourceforge.net/ für Excel, was eine sehr einfache und intuitive API hat, aber beim Lesen oft Probleme bereitet. Nur wurde es bisher seit 2 Jahren nicht mehr aktualisiert. Eine kommerzielle Lösung bietet Aspose (http://www.aspose.com/categories/java-components/aspose.total-for-java/default.aspx). Für den Zugriff auf MS Access Datenbanken gibt es die quelloffene Bibliothek http://jackcess.sourceforge.net/, die auch eine sehr aktuelle fluent API hat. Auf MS Access lässt sich aber auch per ODBC zurückgreifen, das Datenbankkapitel gibt eine kleine Übersicht.
Das neue Office-Format basiert auf XML und so auch deutlich einfacher zu verarbeiten als das binäre Format. Es ist im Office Open XML beschrieben. Für Word-Dokumente gibt es zum Beispiel das relativ neue Projekt java2word (http://code.google.com/p/java2word/).
Neben dem Office Open gibt es das OASIS Open Document Format – mit Dateiformaten wie OpenDocument (ODF) für Textdokumente – die etwa von OpenOffice verarbeitet werden. Eine Bibliothek zum Verarbeiten bietet das ODF Toolkit (http://odftoolkit.org/).
Fehlt noch was?
[1] http://www.microsoft.com/interop/docs/officebinaryformats.mspx
[2] POI steht für „Poor Obfuscation Implementation“, weil das Dateiformat so kryptisch ist. Die Kürzel der Komponenten beginnen in der Regel mit „H“ was für „Horrible“ steht .
Die ultimativen Links zur Java 7, JDK 7 und OpenJDK 7
http://www.tutego.de/java/jdk7-Java-SE-7.htm
Fehlt was?
Java™ Platform, Standard Edition 7 Update 2 Binary Snapshot Releases
http://jdk7.java.net/download.html
Änderungen:
http://hg.openjdk.java.net/jdk7u/jdk7u/ws-b02-2011-08-12_39
Changeset |
Bug ID |
Synopsys |
can’t generate api docs for JDK7 updates |
http://hg.openjdk.java.net/jdk7u/jdk7u/jdk
Changeset |
Bug ID |
Synopsys |
klist does not detect non-existing keytab |
||
Splash screen is not shown in 64-bit Linux with 16-bit color depth |
||
java -help still shows http://java.sun.com/javase/reference |
||
(launcher) java -jar throws NPE if JAR file does not contain Main-Class attribute |
||
PPC: NIO: java.io.IOException: Invalid argument in sun.nio.ch.FileDispatcherImpl.read0 |
http://hg.openjdk.java.net/jdk7u/jdk7u/langtools
Changeset |
Bug ID |
Synopsys |
Attr.PostAttrAnalyzer misses a case |
||
Proposed javac argument processing performance improvement |
||
(javac) allow enabling or disabling of String folding |
||
(javadoc) improve performance on accessing inlinedTags |
||
StringIndexOutOfBoundsException for empty @serialField tag |
||
(javadoc) promote method visibility for netbeans usage |
Videos vom 2011 JVM Language Summit
Deklarative JavaFX-Oberflächen mit FXML
Den Szene-Graph über Java Programmcode aufzubauen ist eine Möglichkeit, doch JavaFX erlaubt es auch, die Objekte über XML zu konfigurieren. Das erlaubt es viel einfacher, grafische Oberflächen über Gui-Builder aufzubauen und sauber das Layout (was) vom Code (wie) zu trennen.
Die hierarchische Struktur von XML passt natürlich prima zu der Hierarchie, die es bei grafischen Oberflächen gibt: Ein Fenster enthält Container, die wiederum Elemente enthalten, usw. Für unser kleines Beispiel soll eine Oberfläche drei Elemente bieten: Eine Beschriftung, ein Textfeld und eine Schaltfläche. Drückt der Anwender die Schaltfläche, soll der Text im Textfeld in Großbuchstaben konvertiert werden.
Die XML-Datei covert2UpperCase.fxml sieht so aus:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.*?> <?import javafx.scene.control.*?> <HBox xmlns:fx="http://javafx.com/fxml" fx:controller="com.tutego.insel.javafx.ButtonController"> <children> <Label text="Eingabe: " /> <TextField fx:id="input" /> <Button text="Konvertiere" onAction="#convertAction" /> </children> </HBox>
Die Hierarchie ist gut zu erkennen. Die interessanten Dinge sind andere:
- Zu Beginn gibt es eine Art import-Anweisung um Typnamen nicht voll qualifizieren zu müssen. Für den Rückgriff auf Grafiken muss <?import javafx.scene.image.*?> eingebunden werden und <?import javafx.scene.*?> für Group, Node oder ähnliches, was wir im Programm aber alles nicht nutzen.
- HBox hat zwei Attribute: Ein Attribut deklariert den Namensraum fx, ein anderes eine Klasse, den sogenannten Controller, der später die Ereignisbehandlung für den Klick übernimmt. Die Typen in FXML heißen genauso wie die Klassennamen. Natürlich könne auch eigene Klassen eingebaut werden, sofern sie mit <?import> bekannt gemacht wurden.
- Das TextField bekommt mit dem Attribut fx:id eine ID zugewiesen, unter der das Textfeld später erfragt werden kann. JavaFX geht noch einen Schritt weiter, und bildet das Objekt mit der ID automatisch auf ein Attribut der Controller-Klasse ab. Label und Schaltfläche brauchen keine IDs, da sie nicht erfragt werden müssen.
- Das Attribut onAction der Schaltfläche referenziert Programmcode, der immer aufgerufen wird, wenn die Schaltfläche gedrückt wird. Hier kann direkt Java-Quellcode stehen, oder, wie in unserem Fall, ein # und der Name einer Methode, die in einem Controller deklariert werden muss. Den Klassenamen vom Controller haben wir am Wurzelelement deklariert.
Die Ereignisbehandlung ist komplett aus der FXML-Datei rausgezogen und wandert in Controller-Klassen. Die eigene Klasse ButtonController, die voll qualifiziert bei fx:controller genannt wurde, enthält:
com/tutego/insel/javafx/ButtonController.java
package com.tutego.insel.javafx; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.TextField; public class ButtonController { @FXML private TextField input; @FXML protected void convertAction( ActionEvent event ) { input.setText( input.getText().trim().toUpperCase() ); } }
Drei Dinge fallen ins Auge:
- Die Controller-Klasse erweitert keine Schnittstelle.
- Die Annotation @FXML sagt, dass der Verweis auf das TextField-Objekt aus dem Szene-Graphen in die Variable input injiziert werden soll.
- Da in der FXML-Datei an der Schaltfläche ein onAction="#convertAction" steht, muss das zu einer Methode zugeordnet werden. Die Annotation @FXML an der Methode unter dem Namen convertAction stellt diese Beziehung her.
Das Hauptprogramm ist nun relativ einfach:
com/tutego/insel/javafx/FXMLDemo.java, start()
@Override public void start( Stage stage ) throws Exception { Parent p = FXMLLoader.load( getClass().getResource( "covert2UpperCase.fxml" ) ); stage.setScene( new Scene( new Group( p ), 500, 400 ) ); stage.setVisible( true ); }
Was noch alles mit FXML möglich ist, beschreibt ein Dokument von Oracle: http://fxexperience.com/wp-content/uploads/2011/08/Introducing-FXML.pdf.
Neuer JavaFX b40 Build
Änderungen: Kein Zip mehr zum Auspacken, sondern eine exe. Keine Demos mehr dabei. APIs ändern sich, viele Beispiele muss ich anpassen. Bei einer Beta ist das natürlich OK, dennoch überlege ich mir, ob ich JavaFX unter diesen Umständen überhaupt in die Insel setzen kann. An großen FX-Apps kann man so nicht denken, die Änderungen machen einen verrückt.
Mein Buchbeispiel ist falsch (und nur einem fällt es auf und sagt es)
Das folgende Code für mein Semaphoren-Beispiel (http://openbook.galileodesign.de/javainsel7/javainsel_10_006.htm) habe ich vereinfacht und ist so nicht wirklich korrekt:
1: static Runnable r = new Runnable() {2: public void run() {3: while ( true ) {4: try
5: {6: semaphore.acquire();7:8: System.out.println( "Thread=" + Thread.currentThread().getName() +
9: ", Available Permits=" + semaphore.availablePermits() );
10: Thread.sleep( 2000 );11: }12: catch ( InterruptedException e ) {
13: e.printStackTrace();14: }15: finally {
16: semaphore.release();17: }18: }19: }20: };Wer findet den Fehler noch?
Nun, in der 10. Auflage ist der Fehler jedenfalls korrigiert und ein Dank geht an Marco Völz.
Antipattern: throw new Exception()
Durch Zufall bin ich bei Heise über http://www.heise.de/developer/artikel/Von-C-nach-Java-Teil-2-Files-I-O-und-eine-Entwicklungsumgebung-1326652.html gestoßen. Eine Implementierung stößt mir auf:
public CFind(String s) throws Exception {
super(s);
if (!isDirectory()) throw new Exception(s+" is NOT a directory!");
for (File f:listFiles()) {
if (f.isDirectory()) {
System.out.println(f.getAbsolutePath());
new CFind(f.getAbsolutePath());
}
}
Es ist keine gute Idee eine direkte Instanz von Exception zu bauen aus auszulösen. Wenn, sollten es immer Unterklasse von Exception sein. In diesem Fall ist das Argument falsch, sodass man ein IllegarArgumentException auslösen könnte. Als zweites gilt throws Exception in der Methodensignatur zu vermeiden – das zwingt Nutzer zur catch(Exception e)-Keule, ganz schlecht, weil dann auch alle RuntimeExceptions aufgefangen werden.
c’t Artikel über Java 7: “Was lange währt …” – eine kurze Nachkritik
In der c’t hat Michael Tamm in der Ausgabe 17 (August) einen Beitrag über Java 7 geschrieben. Der ist ganz gut gelungen und gibt einen netten Einblick in die Neuerungen von Java 7 (präzisiertes rethrow hat er irgendwie verschwiegen, weiß nicht warum).
Ein paar Kleinigkeiten kann man noch anfügen:
- Bei switch mit Strings: “Für jeden case-Zweig wird die in der switch-Anweisung aufgeführte String-Variable mit Hilfe ihrer Methode equals() mit der Konstanten hinter case verglichen”. Hier könnte man meinen, dass eine switch-Anweisung zu einer if-else-Kaskade mit einer linearen Laufzeit führt. Das stimmt aber nicht. equals() kommt erst relativ spät dazu. Erst gibt es einen switch auf dem Hashwert, wenn der passt, kommt equals(), um mit Hashwert-Kollisionen umzugehen.
- “… dass man beim Anlegen von Objekten mit generischen Parametern diese auf der rechten Seiten der Zuweisung nicht mehr wiederholen muss…”. Das könnte man so lesen, dass <> ausschließlich bei Zuweisungen oder Initialisierungen gültig ist. Doch <> ist flexibler; es kann durchaus return new ArrayList<>(); heißen. (Dass der Begriff “generischer Parameter” unsauber ist, ist eine andere Sache … )
- Die Einleitung vom multi-catch beginnt mit “… Seit Java 1.0 gibt es geprüfte (checked exceptions), die immer behandelt oder in der Methodensignatur aufgeführt werden müssen, sowie die ungeprüfte Ausnahmen […].“ Ob multi-catch nun checked oder unchecked Exception fängt ist egal, daher ist die Information an diese Stelle eigentlich überflüssig.
- Beim Thema try-mit-Ressourcen “Die VM schließt alle hinter dem try in runden Klammern aufgeführten Ressourcen[…]”. Irgendwie macht alles schon die JVM, aber man könnte den Eindruck bekommen, hier ist Magie im Spiel. Das stimmt aber nicht, denn der Compiler erzeugt nur etwas, was wir hätten auch schreiben können, nur haben wir mit dem neuen try weniger Schreibarbeit. Die JVM weiß nicht, ob der tolle Schließ-Code von uns kommt oder nicht.
- “Das Pendant zur alten Klasse File ist die neue Klasse Path”. Pendant? Klingt, als ob die gleich sind, ist aber absolut nicht so. Wenn das ein Pendant, also eine Kopie wäre, warum dann NIO.2?
Die beschriebenen Dinge sind nur missverständlich, aber leider gibt es auch zwei dickere Falschaussagen:
- “Mit den in Java 7 eingeführten Typannotationen lassen sich nun auch Typen näher beschreiben”. Es folgt das Beispiel “@NonNull Set<@NonNull Edges> edges;” und er verweist auf den Link von JSR-308 (http://types.cs.washington.edu/jsr308/). Wäre nett, ist aber Blödsinn! Es gibt in Java 7 keine Änderung. Das verlinkten Dokument sagt: “Type annotations are planned to be part of the Java language and are supported by Oracle’s OpenJDK (for JDK 7, as of build M4).” Das JST-308 nicht von Java 7 ist hatte ich auch schon vor einem halben Jahr geschrieben: http://www.tutego.de/blog/javainsel/2011/02/java-se-7-developer-preview-release-verfgbar/, aber ich erwarte nicht, dass Herr Tamm mein Blog liest.
- “[…] Nimbus Look & Feel […] ist standardmäßig aktiv”. Hätte Michael nur ein Swing-Programm gestartet, wäre ihm aufgefallen, das immer noch Metal aktiv ist und nicht Nimbus. (Grund: Inkompatibilitäten http://blogs.oracle.com/henrik/entry/nimbus_look-and-feel_in_jdk_7). Das SwingSet Demo wählt nur automatisch Nimbus aus.
Architecture Explorer zur Analyse von Java-Architekturen
Ein Video erklärt die Web-Anwendung (sehr ungewöhnlich für so ein Tool)
d
Live-Demo unter http://xplrarc.massey.ac.nz/index.jsp.
App Engine 1.5.3 Update
Siehe http://googleappengine.blogspot.com/2011/08/app-engine-153-sdk-released.html, http://code.google.com/p/googleappengine/wiki/SdkForJavaReleaseNotes:
- Blobstore API – We’ve removed the limits on the size of blob uploads. You can now upload files of any size, allowing your app to serve images, video, or anything your internet connection can handle.
- Index retrieval – We’ve added the ability for you to programmatically retrieve the list of indexes you’ve currently defined in the datastore, as well as their statuses.
- Datastore Admin – You can now enable the Datastore Admin function from the Admin Console. This will allow Java users to make use of this functionality, like deleting all entities of a certain kind, without having to upload a Python version of their application. And for Python developers, you no longer need to enable this in your app.yaml file.
- HRD Migration Trusted Testers – We are seeking early adopters to try out an improved HRD migration tool that requires a read-only period relative to your datastore write rate (as opposed to your datastore size, which is how the current version behaves). Please see the release notes for more information.
- Download app – Using the AppCfg download_app command, you can download any files that were uploaded from your war directory when you last updated the app version.
Sehr sehr cool: Swing-Anwendungen im Browser
Schaut euch
- http://icedrobot.de:9091/SessionInitializer?cls=SwingSet2
- http://icedrobot.de:9091/SessionInitializer?cls=Notepad (Strg+A funktioniert nicht, rechte Maustaste auch nicht)
an.
Interessant für Anwender mobiler Endgeräte, die kein Swing können (Android) oder dürfen (iPad).
Die HTML-Anwendung besteht aus einem bisschen JavaScript, die Interaktionen zum Server schickt, also Mausklicks, Tastendrücke, usw. Dabei entstehen Events wie
MM_984_567_MM_986_567_
die an http://icedrobot.de:9091/EventReceiver per POST gesendet werden. Die Swing-Anwendung (die werden dafür richtig viel Speicher brauchen denke ich, das kommt vielleicht noch mal raus…) läuft nur auf dem Server und der reagiert mit geänderten Bildschirmausschnitten, die wieder auf den Brower Canvas gezeichnet werden. Ist so wie ein remote VNC-Server (http://code.google.com/p/jsvnc/), schöne Sache das. HTML5 wir lieben dich
“Schön” im Sourcecode der Kommentar:
//Exclude WebKit for now, as it will trigger a memory leak.
//TODO: should be version dependent, as its fixed in Chrome 14
Nach endlich, Google legt bei den Patenten nach
Jetzt auch bei Google+, Einladungen
Ich habe noch Einladungen bei Google+: https://plus.google.com/_/notifications/ngemlink?path=%2F%3Fgpinv%3Dk4jYjYo4HX4%3AYOvNkIeCnKA. Selbst habe ich ein Profil unter https://profiles.google.com/ullenboom, wer möchte, kann mich gerne hinzufügen, aber ich weiß noch nicht so genau, was ich dort schreiben werde. Die Java-Sachen bleiben auf dem Blog.
Swing-Bibliothek JIDE in der Version 3.2.0
Die News unter http://www.jidesoft.com/company/news.htm. Neu unter anderem:
Sehr interessant finde ich den (mutigen?) Schritt:
This means we will now officially stop supporting JDK5.
Die Komponentensammlung kostet Geld, frei ist http://www.jidesoft.com/products/oss.htm.
Interessantes Mathe/Logik-Buch – Fehler in der Didaktik einsetzen
Attila Furdek ist Mathelehrer und hat ein interessantes Buch veröffentlicht: http://www.amazon.de/Fehler-Beschw%C3%B6rer-Typische-Fehler-L%C3%B6sen-Mathematikaufgaben/dp/3831121109 (http://www.bod.de/index.php?id=296&objk_id=51857). Ich finde den Ansatz toll: Er stellt eine Aufgabe vor und suggeriert richtige Lösungen, die aber falsch sind. (Bei Amazon kann man sich ein paar Sachen anschauen.) Der Lernende muss zeigen, warum der Lösungsweg falsch ist. Mit gefällt die Idee gut. In meinen Java-Kursen mache ich am Anfang etwas ähnliches: ich gebe syntaktisch falschen Programmcode vor (Klammern fehlen, Variablendeklarationen fehlen, Initialisierungen fehlen, Semikolon fehlt, usw.), und die Lernenden müssen ohne Compilermeldungen die Fehler finden. Wer die meisten Fehler findet, ist Bug-König. Danach wird aufgelöst und die die Fehler mit dem Compiler abgeglichen, um die Meldungen des Compilers zu lernen und mit den Fehlertypen in Verbindung zu bringen.
Ein dämliches Patent: Verallgemeinerte verkettete Listen
Der “Trick”: Es gibt nicht nur einen “Next”-Zeiger, sondern einen “Aux”-Zeiger, der auf alle möglichen “Next”-Knoten zeigen kann. Der Hammer… Details: http://www.google.com/patents/about?id=26aJAAAAEBAJ.
Aneinanderreihung von Comparatoren
Oftmals ist das Ordnungskriterium aus mehreren Bedingungen zusammengesetzt, wie die Sortierung in einem Telefonbuch zeigt. Erst gibt es eine Sortierung nach dem Nachnamen, dann folgt der Vorname. Um diese mit einem Compartor-Objekt zu lösen, müssen entweder alle Einzelvergleiche in ein neues Compartor-Objekt verpackt werden, oder einzelne Comparatoren zu einem „Super“-Comparator zusammengebunden werden – die zweite Lösung ist natürlich schöner, denn das erhöht die Wiederverwendbarkeit, denn einzelne Comparatoren können dann leicht für andere Zusammenhänge genutzt werden.
Comparatoren in eine Vergleichskette setzen
Am Anfang steht ein besonderer Comparator, der sich aus mehreren Comparatoren zusammensetzt. Immer dann, wenn ein Teil-Compartor bei zwei Objekten aussagt, dass sie gleich sind (der Vergleich liefert 0 ist), so soll der nächste Comparator die Endscheidung fällen – kann er das auch nicht, weil das Ergebnis wieder 0 ist, geht es zum nächsten Vergleicher.
Den Programmcode wollen wir einen neue Hilfsklasse ComparatorChain setzen:
package com.tutego.insel.util; import java.util.*; /** * A {@link Comparator} that puts one or more {@code Comparator}s in a sequence. * If a {@code Comparator} returns zero the next {@code Comparator} is taken. */ public class ComparatorChain<E> implements Comparator<E> { private List<Comparator<E>> comparatorChain = new ArrayList<Comparator<E>>(); /** * Construct a new comparator chain from the given {@code Comparator}s. * The argument is not allowed to be {@code null}. * @param comparators Sequence of {@code Comparator}s */ @SafeVarargs // ab Java 7 public ComparatorChain( Comparator<E>... comparators ) { if ( comparators == null ) throw new IllegalArgumentException( "Argument is not allowed to be null" ); Collections.addAll( comparatorChain, comparators ); } /** * Adds a {@link Comparator} to the end of the chain. * The argument is not allowed to be {@code null}. * @param comparator {@code Comparator} to add */ public void addComparator( Comparator<E> comparator ) { if ( comparator == null ) throw new IllegalArgumentException( "Argument is not allowed to be null" ); comparatorChain.add( comparator ); } /** * {@inheritDoc} */ @Override public int compare( E o1, E o2 ) { if ( comparatorChain.isEmpty() ) throw new UnsupportedOperationException( "Unable to compare without a Comparator in the chain" ); for ( Comparator<E> comparator : comparatorChain ) { int order = comparator.compare( o1, o2 ); if ( order != 0 ) return order; } return 0; } }
Die ComparatorChain können wir auf zwei Weisen mit den Comparator-Gliedern füttern: einmal zu Initialisierungszeit im Konstruktor, und dann später noch über die addComparator()-Methode. Beim Weg über den Konstruktor ist ab Java 7 die Annotation @SafeVarargs zu nutzen, da sonst die Kombination eines Varargs und Generics auf der Nutzerseite zu einer Warnung führt.
Ist kein Comparator intern in der Liste, wird das compare() eine Ausnahme auslösen. Der erste Comparator in der Liste ist auch das Vergleichsobjekt was zuerst gefragt wird. Liefert er ein Ergebnis ungleich 0 liefert das die Rückgabe der compare()-Methode. Ein Ergebnis gleich 0 führt zur Anfrage des nächstes Comparators in der Liste.
Wir wollen diese ComparatorChain für ein Beispiel nutzen, dass eine Liste nach Nach- und Vornamen sortiert.
package com.tutego.insel.util; import java.util.*; public class ComparatorChainDemo { public static class Person { public String firstname, lastname; public Person( String firstname, String lastname ) { this.firstname = firstname; this.lastname = lastname; } @Override public String toString() { return firstname + " " + lastname; } } public final static Comparator<Person> PERSON_FIRSTNAME_COMPARATOR = new Comparator<Person>() { @Override public int compare( Person p1, Person p2 ) { return p1.firstname.compareTo( p2.firstname ); } }; public final static Comparator<Person> PERSON_LASTNAME_COMPARATOR = new Comparator<Person>() { @Override public int compare( Person p1, Person p2 ) { return p1.lastname.compareTo( p2.lastname ); } }; public static void main( String[] args ) { List<Person> persons = Arrays.asList( new Person( "Onkel", "Ogar" ), new Person( "Olga", "Ogar" ), new Person( "Peter", "Lustig" ), new Person( "Lara", "Lustig" ) ); Collections.sort( persons, PERSON_LASTNAME_COMPARATOR ); System.out.println( persons ); Collections.sort( persons, PERSON_FIRSTNAME_COMPARATOR ); System.out.println( persons ); Collections.sort( persons, new ComparatorChain<Person>( PERSON_LASTNAME_COMPARATOR, PERSON_FIRSTNAME_COMPARATOR ) ); System.out.println( persons ); } }
Die Ausgabe ist:
[Peter Lustig, Lara Lustig, Onkel Ogar, Olga Ogar]
[Lara Lustig, Olga Ogar, Onkel Ogar, Peter Lustig]
[Lara Lustig, Peter Lustig, Olga Ogar, Onkel Ogar]