JSR 203: Die neuen I/O-Schnittstellen in Java 7

Man mag es kaum glauben, aber manche Probleme, die mit dem neuen Java 7 endlich gelöst wurden, haben Programmierern ein ganzes Jahrzehnt lang Zeit und Nerven geraubt - allen voran die Programmierschnittstellen (APIs) zum Umgang mit Dateien, Verzeichnissen und anderen alltäglichen Bestandteilen eines Computersystems. Doch damit ist jetzt Schluss: Der Java Specification Request (JSR) 203 hat seinen Weg in die siebte Version der Software Edition von Java gefunden und mit ihm eine ganze Reihe neuer Schnittstellen, die die Herzen der Programmierer höher schlagen lassen werden.

Doch bevor es richtig technisch wird und wir uns die neuen Schnittstellen anhand diverser Code-Beispiele näher ansehen, brennt vermutlich vielen Lesern die Frage auf den Nägeln, was denn mit der derzeitigen Implementation so verkehrt sei.

In einem Interview bringt es Alan Bateman von Oracle sehr deutlich auf den Punkt:

Die derzeitige Implementation ...

  • ist extrem ineffizient im Umgang mit großen Verzeichnissen,
  • kann nicht sonderlich gut mit symbolischen Verlinkungen umgehen,
  • hat keine Operationen zum Kopieren und Verschieben einer Datei,
  • erlaubt nur auf relativ wenige Datei-Attribute direkten Zugriff,
  • führt diese Attributsabfragen relativ rechenintensiv durch,
  • liefert beim Scheitern einer I/O-Operation keine weiteren Details.

Oder in Kurzform: Die derzeitige Implementation ist nicht mehr zeitgemäß und musste dringend überarbeitet werden.

Dank JSR 203 und den Leuten rund um Alan Bateman bietet Java in der Version 7 eine ganze Reihe neuer Methoden und Möglichkeiten, die noch weit über das hinausgehen, was bislang fehlte. In diesem Artikel wollen wir einen kurzen (unvollständigen) Blick auf die neuen Möglichkeiten werfen, die java.nio.file.* bietet.

Die Schnittstelle Path und ihre Methoden

Wer mit den Dateien und Verzeichnissen eines Betriebssystem arbeiten will, muss wissen, wo diese liegen und wie er auf sie verweist. Genau darum kümmert sich die neue Schnittstelle Path, in der alle Methoden gesammelt wurden, mit denen sich Pfadangaben bearbeiten und manipulieren lassen.

Um eine Referenz auf das eigene Home-Verzeichnis /home/user/ zu erhalten, würde man beispielsweise den folgenden Code schreiben:

FileSystem FS   = FileSystems.getDefault();
Path       home = FS.getPath(System.getProperty("user.home"));

Sieht irgendwie umständlich aus? Das dachten sich die Java-Entwickler auch und entwarfen kurzerhand eine Klasse Paths mit nur einer einzigen Methode get(), die beliebig viele Zeichenketten zu einem Pfad verdrahtet:

Path home = Paths.get(System.getProperty("user.home"));

Will man nun einen Pfad zur Datei .bash_profile und gleichzeitig einen Pfad zu einem Unterordner backups basteln, wo man diese Datei später sicherheitshalber abspeichern wird, so könnte man dies wie folgt lösen:

Path profile = home.resolve(".bash_profile");
// /home/user/.bash_profile

Path backup
= Paths.get(home.toString(), "backups", ".bash_profile");
// /home/user/backups/.bash_profile

Will man von backup ausgehend eine relative Pfadangabe zu profile erzeugen, so hilft einem die Methode relativize() weiter:

Path backup2profile = backup.relativize(profile);
// ../.bash_profile

Wer mehr über die Methoden der Schnittstelle Path erfahren will, sollte einen Blick in die Dokumentation oder die Tutorials von Oracle werfen.

Die Klasse Files und ihre Datei-Methoden

Lange musste man warten, aber nun ist sie da: Eine Klasse voller Methoden, um mit Dateien zu arbeiten. Kopieren, verschieben, anlegen, löschen und mehr lässt sich nun einfach "out of the box" bewältigen.

Würden wir eine Datei mit voreingestellten Attributen anlegen wollen, könnten wir dies so tun:

Files.createFile(backup);

Was aber, wenn wir nun im Nachhinein die Rechte ändern wollen? Kein Problem! Der folgende Code passt die Datei-Attribute so an, dass nur noch der Eigentümer – also wir – die Datei lesend und schreibend verwenden darf:

Set<posixfilepermission> perms
= PosixFilePermissions.fromString("rw-------");

Files.setPosixFilePermissions(backup, perms);

In unserem konkreten Beispiel wollen wir die Datei jedoch lediglich kopieren, und es wäre doch am einfachsten, wenn die Kopie die gleichen Rechte wie das Original hat, oder? Das fanden auch die Oracle-Leute und entwickelten eine Methode copy(), der man Optionen mitgeben kann:

Files.copy(profile, backup, StandardCopyOption.COPY_ATTRIBUTES);

Nun haben wir eine Kopie erzeugt, die mit genau den gleichen Rechten wie unser Original angelegt wurde. Um zu überprüfen, ob wir die Datei lesen, beschreiben und ausführen dürfen, gibt es noch drei weitere kleine Hilfsfunktionen: isReadable(), isWritable() und isExecutable().

Und wollen wir die Datei doch wieder löschen, so hilft uns die Methode deleteIfExists() weiter, die sich eine Fehlermeldung spart, sofern die Datei gar nicht existiert:

Files.deleteIfExists(backup);

Wird die Originaldatei zerstört und möchten wir unsere Kopie an ihre Stelle bewegen, ist uns die Methode move() behilflich:

Files.move(backup, profile, StandardCopyOption.REPLACE_EXISTING);

In einem realen Szenario wäre diese Taktik natürlich eine Katastrophe, da wir unsere Backup-Datei verschieben und beim nächsten Crash kein Backup mehr zur Verfügung hätten! Besser wäre deshalb ein Aufruf von copy() mit der Option REPLACE_EXISTING, wodurch unser Backup bleibt, wo es ist, und das Original wiederhergestellt wird.

Auch für ausführliche Informationen über die Methoden der Klasse Files empfiehlt sich ein Blick in die Dokumentation oder die Tutorials von Oracle.

Die Klasse Files und ihre Verzeichnis-Methoden

In unserem bisherigen Szenario haben wir vorausgesetzt, dass unser Backup-Verzeichnis bereits existiert. Was aber, wenn bislang niemand diesen Ordner angelegt hat? Dann ist es nun Zeit dies endlich zu tun – und zwar wieder mithilfe der Klasse Files.

Sind uns die Verzeichnisrechte zunächst egal und die Default-Werte ausreichend, helfen folgende Zeilen beim Anlegen eines Ordners weiter:

Path backupDir
= Paths.get(System.getProperty("user.home"), "backups");

Files.createDirectory(backupDir);

Allzu häufig sind die Verzeichnisrechte jedoch nicht egal und so könnte man etwa erwarten, dass der Backup-Ordner nur von uns selbst gelesen und beschrieben werden darf. Auf einem POSIX-konformen System wie Linux oder Mac OS X sähe der Code, der dies löst, wie folgt aus:

Set</posixfilepermission><posixfilepermission> perms
= PosixFilePermissions.fromString("rw-------");

FileAttribute<set <PosixFilePermission>> attrs
= PosixFilePermissions.asFileAttribute(perms);

Files.createDirectory(backupDir, attrs);

Eine der typischsten Operationen beim Arbeiten mit Verzeichnissen ist das Auflisten der Inhalte – häufig nach bestimmten Regeln gefiltert. In Java 7 geht dies dank der Schnittstelle DirectoryStream, die widerum von Iterable erbt, richtig locker von der Hand: Die Inhalte des Ordners können nacheinander mit einer einfachen For-Each-Schleife abgefragt werden.

Will man die Inhalte bereits gefiltert erhalten und nur *.java- bzw. *.class-Dateien betrachten, ergäbe dies für das eigene Home-Verzeichnis folgenden Code, der einfacher kaum sein könnte:

try (DirectoryStream<path> stream
= Files.newDirectoryStream(home, "*.{java,class}"))
{
    for (Path entry: stream)
    {
        System.out.println(entry.getFileName());
    }
} catch (IOException e) { /* ... */ }

In diesen wenigen Zeilen Code versteckt sich sogar noch ein weiteres Feature, das in Java 7 neu hinzugekommen ist: Der Try-Catch-Anweisung wird der DirectoryStream direkt als Argument übergeben und am Ende auch wieder automatisch geschlossen. Ein manueller Aufruf von close(), wie er im bislang im Finally-Teil üblich war, entfällt.

Was jedoch, wenn man lieber seinen ganz eigenen Filter entwickeln will? Sie ahnen es sicherlich schon: Auch das ist überhaupt kein Problem. Um einen eigenen Filter zu schreiben, muss man lediglich DirectoryStream.Filter implementieren, ein Interface, dessen einzige Methode accept() ist.

Diese liefert entweder True oder False zurück – je nachdem, ob der Eintrag verwendet oder verworfen werden soll. Einen Filter, der ausschließlich Ordner zurückliefert, würden wir wie folgt realisieren:

DirectoryStream.Filter</path><path> filter
= newDirectoryStream.Filter</path><path>()
{
    public boolean accept(Path p) throws IOException
    {
        try
        {
            return (Files.isDirectory(p));
        }
        catch (IOException e) { /* ... */ return false; }
    }
};

Mehr über die Methoden der Klasse Files erfahren Sie ebenfalls in der Dokumentation oder den Tutorials von Oracle.

Fazit

In Java 7 haben etliche Neuerungen Einzug erhalten, aber die neuen I/O-Schnittstellen gehören mit einem Jahrzehnt Wartezeit definitiv zu den am längsten herbeigesehnten Verbesserungen. Im Sinne der Todo-Liste, die wir am Anfang aufgestellt haben, wollen wir nun die finale Done-Liste liefern:

Die neue Implementation...

  • kann mit großen Verzeichnissen effizient umgehen,
  • kann mit symbolischen Verlinkungen problemlos arbeiten,
  • hat eine neue Klasse zum Arbeiten mit Dateien und Ordnern,
  • erlaubt auf viele verschiedene Datei-Attribute direkten Zugriff,
  • führt diese Attributs-Abfragen ressourcensparend durch,
  • liefert beim Scheitern einer I/O-Operation detaillierte Fehlermeldungen.

Oder in Kurzform: Mission accomplished!

Weiterführende Informationen

Ihr neues Web-Projekt wird mit Java entwickelt? Sie möchten ein bestehendes Java-System erweitern oder eine Software-Plattform migrieren? Sie benötigen Schnittstellen zwischen Anwendungen im Unternehmen? //SEIBERT/MEDIA ist der richtige Partner. Wir legen größten Wert auf iterative Erweiterbarkeit, Performanz, Skalierbarkeit, Testbarkeit und Plattformunabhängigkeit und schaffen so individuelle High-End-Software-Lösungen, die sich auch im Nachhinein flexibel ausbauen und verändern lassen. Bitte sprechen Sie uns unverbindlich an!

Unsere spezielle Seite zum Thema Software-Entwicklung mit Java
Welche Neuerungen sind mit Java 7 zu erwarten?
GWT: Evolution der Internet-Anwendung
Coding-Katas: Steigerung der Produktivität und höhere Code-Qualität


Mitwirkender an Konzeption und Umsetzung: Micha Kops
Bild: Javas Maskottchen von Mokund unter CC-Lizenz


Mehr über die Creative-Commons-Lizenz erfahren

ACHTUNG!
Unsere Blogartikel sind echte Zeitdokumente und werden nicht aktualisiert. Es ist daher möglich, dass die Inhalte veraltet sind und nicht mehr dem neuesten Stand entsprechen. Dafür übernehmen wir keinerlei Gewähr.

Schreibe einen Kommentar