Federlesen #12: Zusatzfunktionen für Java mit Apache Commons

Ein Erfolgsfaktor der Programmiersprache Java ist, dass von Anfang an ein umfangreicher Satz an Zusatzfunktionen dazugehörte. Trotzdem blieb immer Raum für das Apache-Commons-Projekt, das sich als Fundus für weitere Ergänzungen und Erweiterungen sieht.

In Pocket speichern vorlesen Druckansicht 3 Kommentare lesen
Lesezeit: 10 Min.
Von
  • Frank Pientka
Inhaltsverzeichnis

Ein Erfolgsfaktor der Programmiersprache Java ist, dass von Anfang an ein umfangreicher Satz an Zusatzfunktionen dazugehörte. Trotzdem blieb immer Raum für das Apache-Commons-Projekt, das sich als Fundus für weitere Ergänzungen und Erweiterungen sieht.

Seit 2007 existiert Apache Commons als eigenständiges Hauptprojekt, das ursprünglich aus Jakarta Commons und Features anderer Apache-Projekte wie Excalibur oder Tomcat hervorging. Es wird in vielen Apache-, aber auch anderen Softwareprojekten als eine Art Funktionsbibliothek für Java verwendet. Es lässt sich grob in die folgenden Bereiche einteilen:

  • Web, Mail (mit den Bibliotheken FileUpload, Net, EL, Email, Jexl)
  • XML (mit Bibliotheken wie Betwixt, Digester, Jelly, JXPath)
  • Utilities (mit BeanUtils, Pool, Validator, Daemon, Discovery, Exec, Launcher, JCI, Jelly, Modeler, SCXML, Chain)
  • Konvertierungen (mit Bibliotheken wie Codec, Compress, CSV)
  • Java-API-Erweiterungen (mit den Bibliotheken Lang, Collections, IO, Logging, Configuration, DBCP, DbUtils, Math, Primitives, Proxy, Validator, VFS, Attributes, CLI, Discovery, Transaction, Sanselan)
  • HttpComponents (mit Core IO/NIO, Client, AsyncClient)

Eine Besonderheit des Projekts ist, dass die dahinter stehenden Entwickler äußerst behutsam mit Versionsänderungen umgehen und möglichst lang ältere Java-Versionen unterstützen. Um API-Änderungen bei gleichzeitiger Kompatibilität zur alten API im Parallelbetrieb einzuführen, verwenden sie wie bei lang3 und math3 neue Paketnamen. Diese größeren Änderungen werden in einem eigenen Migrations-Guide oder in den deprecated-Kommentaren beschrieben, was beim Umstieg auf die neue API zu berücksichtigen ist.

Commons unterscheidet zwischen produktiven Bibliotheken (Proper genannt), unter denen die oben genannten zu finden sind, den experimentellen in der Sandbox und "eingeschlafenen" im eigenen sogenannten Dormant-Bereich. Eine Ausnahme bilden aufgrund ihrer Funktionsvielfalt und großen Bedeutung die HttpComponents, die ein eigenständiges Top-Level-Projekt sind. Alle Commons-Projekte verwenden eine Versionsverwaltung und die beiden Mailinglisten user und dev. Nur zu wenigen Bibliotheken gibt es ein eigenes Benutzerhandbuch, hier helfen entweder die mitgelieferten Beispiele oder die gute Javadoc-Beschreibung weiter.

ASCII ist die Mutter aller Zeichenkodierungen. Als kleinster gemeinsamer Nenner zur Kodierung spielt sie beim Austausch von Informationen immer noch eine große Rolle. Der Internet-Standard MIME (Multipurpose Internet Mail Extensions rfc4648) verwendet die Zeichenkodierung, um mit einer Base64-Kodierung 8-Bit-Binärdaten in eine Zeichenfolge umzuwandeln. Bisher konnte man in Java nur die BASE64Encoder/BASE64Decoder-Klassen aus dem nicht öffentlichen Paket sun.misc verwenden, wovon aus Kompatibilitätsgründen abgeraten wird, oder mit der javax.mail.internet.MimeUtility-Klasse aus Java EE arbeiten. Das kommende Java 8 wird dafür endlich die Klasse java.util.BASE64 enthalten. Wer nicht so lange warten wollte, konnte die Base64-Kodierung schon länger mit Commons Codec verwenden. Außerdem enthält die Bibliothek die zwei nützlichen Klassen RefinedSoundex für englische Namen und ColognePhonetic (die Kölner Phonetik nach Hans Joachim Postel) für deutsche Worte, um Rechtschreibfehler zu erkennen. So ergeben die Namen "Müller-Lüdenscheidt" und "Mueller-Langenscheidt" phonetisch den gleichen Wert:

String name1 = new ColognePhonetic()
.colognePhonetic("Müller-Lüdenscheidt");
String name2 = new ColognePhonetic()
.colognePhonetic("Mueller-Langenscheidt");
System.out.println(name1 + " " + name2 + " = "
+ new ColognePhonetic().isEncodeEqual(name1, name2));

Drei Methoden, die jede Java-Klasse durch die Vererbung von Object hat, sind equals, hashCode und toString. Um sie korrekt zu überschreiben, ist bei Klassen mit mehr Attributen viel Tipparbeit nötig, die heutige IDEs durch Codegenerierung abnehmen. Apache Commons Lang3 löst das elegant über die Klassen EqualsBuilder, HashCodeBuilder und ToStringBuilder, die, wie ihre Namen sagen, das Builder-Pattern umsetzen. Als wartungsfreundliche und platzsparende Alternative bieten diese Klassen, wie bei der ToStringBuilder.reflectionToString-Methode im Beispiel gezeigt, das dynamische Zusammenstellen der Klassenattribute:

public boolean equals(Object obj) {
if (obj instanceof Person) {
Person other = (Person) obj;
EqualsBuilder builder = new EqualsBuilder();
builder.append(id, other.id);
builder.append(name, other.name);
return builder.isEquals();
}
return false;
}

public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(id).append(name).append(age).toHashCode();
}
public String toString() {
return ToStringBuilder.reflectionToString(this);

Weitere nützliche Klassen aus der Lang3-Bibliothek sind die StringUtils, mit denen man durch eine Funktion wie isBlank ohne komplizierte Abfrageverschachtelungen die Initialisierung von Strings abfragen kann. Ebenfalls hilfreich sind StopWatch zum einfachen Messen von Ausführungszeiten und DateFormatUtils zum Formatieren von Datumsangaben. Gleiches gilt für die System-Properties mit der Klasse SystemUtils. Zum Erzeugen von Testdaten helfen die Klasse RandomStringUtils aus Lang3 und die vielen Klassen für Zufallszahlen im Math3-Paket weiter.

Die korrekte BASE64-De- und -Encodierung wird in folgendem Beispiel abhängig vom verwendeten Betriebssystem ausgeführt:

StopWatch clock = new StopWatch();
clock.start();
if (SystemUtils.IS_JAVA_1_7 && SystemUtils.IS_OS_WINDOWS_7)
{ byte[] binaryData = RandomStringUtils.
randomAlphanumeric(20).getBytes();
String base64String = Base64.encodeBase64String(binaryData);
if (!isBlank(base64String))
System.out.println(base64String);
byte[] decodedData = Base64.decodeBase64(base64String);
System.out.println("correct de-/encode = "
+ Arrays.equals(binaryData, decodedData));
} else
System.out.println("wrong Java Version");
clock.stop();
System.out.println("Duration " + clock.getTime() + " ms");