Java File-Klasse
Dateisystempfade in Java mit der Legacy-Klasse java.io.File darstellen — exists, isFile, isDirectory, listFiles.
java.io.File ist der ursprüngliche „Dieser String ist ein Pfad"-Typ aus Java 1.0. Die Klasse selbst führt kein I/O durch — sie öffnet, liest oder schreibt keine Daten — sie benennt lediglich einen Speicherort im Dateisystem und bietet eine Handvoll Methoden, um das Betriebssystem über diesen Ort zu befragen und einzelne Operationen darauf auszuführen (exists, isDirectory, delete, renameTo, listFiles).
java.nio.file.Path (Java 7) ist der moderne Ersatz, den neuer Code verwenden sollte. Dennoch begegnet man File in jeder Codebasis, die älter als ~2012 ist, und viele ältere APIs akzeptieren und geben ihn zurück. Dieses Kapitel behandelt, was die Klasse leistet, wo die Grenzen liegen und wie sie sich mit Path verbindet.
Konstruktion
Ein File umhüllt einen Pfad-String. Vier Konstruktoren decken die häufigsten Fälle ab:
File a = new File("data/users.txt"); // relative to the JVM's working directory
File b = new File("/var/log/app.log"); // absolute
File c = new File("/tmp", "session.txt"); // parent + child
File d = new File(new File("/tmp"), "session.txt"); // parent File + childDer Konstruktor führt keine Validierung durch — ein unsinniger Pfad konstruiert ein File problemlos; erst beim Aufruf von exists(), delete() usw. wird das Betriebssystem einbezogen.
Verwende den Zwei-Argument-Konstruktor für „Elternteil + Name" anstelle von String-Verkettung. Er wählt das richtige Trennzeichen (/ unter Unix, \ unter Windows) und vermeidet den Fehler, bei dem der Elternpfad möglicherweise mit einem Trennzeichen endet oder nicht:
File good = new File(parentDir, "data.txt"); // separator handled for you
File bad = new File(parentDir + "/data.txt"); // brittle: depends on parentDir's exact stringAbfragen des Dateisystems
File bietet eine breite Palette von Abfragen, die boolean- und long-Werte zurückgeben. Die häufigsten:
File f = new File("data/users.txt");
f.exists(); // does the path point to anything?
f.isFile(); // is it a regular file?
f.isDirectory(); // is it a directory?
f.isHidden(); // hidden by OS convention (leading dot on Unix, hidden attr on Windows)
f.length(); // size in bytes (0 for a directory)
f.lastModified(); // epoch millis; 0 if it doesn't exist or can't be queried
f.canRead(); // permission check from the JVM's point of view
f.canWrite();
f.canExecute();Jeder dieser Aufrufe trifft das Betriebssystem. Sie sind einzeln günstig, aber nicht kostenlos — exists(), isDirectory() und length() nacheinander aufzurufen bedeutet drei Syscalls. Wenn man mehrere Attribute einer Datei benötigt, ist Files.readAttributes(path, BasicFileAttributes.class) (nächster Teil) ein einziger Syscall.
Pfadansichten
File bietet mehrere Möglichkeiten, denselben zugrunde liegenden String zu betrachten:
File f = new File("data/../data/users.txt");
f.getName(); // "users.txt" — last component
f.getParent(); // "data/../data" — String, or null at the root
f.getParentFile(); // File for the parent, or null
f.getPath(); // "data/../data/users.txt" — what you constructed
f.getAbsolutePath(); // resolved against working dir, NOT canonicalised
f.getCanonicalPath(); // resolved, normalised, symlinks followed — can throw IOExceptiongetAbsolutePath und getCanonicalPath sind das am häufigsten verwechselte Paar der Klasse:
getAbsolutePath— stellt das aktuelle Arbeitsverzeichnis der JVM voran, wenn der Pfad relativ ist. Gibt den String mit..-Segmenten zurück, wie sie sind.getCanonicalPath— wie absolut, aber löst zusätzlich..und.auf und folgt symbolischen Links. Kann auf die Festplatte zugreifen undIOExceptionwerfen.
Bei sicherheitssensitiven Prüfungen (befindet sich dieser vom Benutzer angegebene Pfad innerhalb des erlaubten Verzeichnisses?) ist getCanonicalPath die einzig sichere Variante — andernfalls kann ein relativer Pfad wie safe-dir/../../../etc/passwd einen startsWith("safe-dir")-Check umgehen.
Ein Verzeichnis auflisten
Vier Varianten, zwei Paare:
File dir = new File("/tmp");
String[] names = dir.list(); // child names, no metadata
File[] children = dir.listFiles(); // child File objects
String[] txt = dir.list((d, name) -> name.endsWith(".txt")); // FilenameFilter
File[] files = dir.listFiles(File::isFile); // FileFilterSowohl FilenameFilter als auch FileFilter sind funktionale Interfaces mit einer einzigen Methode (Vokabular aus Teil 12), daher funktionieren Lambda-Ausdrücke oder Methodenreferenzen direkt. Der Unterschied: FilenameFilter erhält das Elternverzeichnis und den reinen Namen; FileFilter erhält das konstruierte Kind-File. Verwende FileFilter, wenn du isDirectory() oder length() zur Entscheidung aufrufen musst; FilenameFilter genügt, wenn Namensabgleich ausreicht.
Alle vier Methoden geben null zurück, wenn der Pfad kein Verzeichnis ist — sie werfen keine Ausnahme. Das ist eine klassische NPE-Quelle:
for (File child : dir.listFiles()) { ... } // NPE if dir is not a directory!
File[] children = dir.listFiles();
if (children != null) for (File c : children) { ... } // correctDas moderne Files.list(path) gibt einen leeren Stream<Path> für ein fehlendes Verzeichnis zurück oder wirft eine klare NotDirectoryException. Die File-API gibt einfach null zurück und lässt das Programm abstürzen.
Erstellen, Löschen, Umbenennen
File bietet einige Mutationsmethoden:
f.createNewFile(); // creates an empty file; returns boolean; throws IOException on real failure
f.mkdir(); // creates this directory; parent must exist
f.mkdirs(); // creates this directory and any missing parents
f.delete(); // deletes this file or empty directory; returns boolean
f.renameTo(other); // OS-specific behaviour; returns booleanDas wiederkehrende Thema — boolean-Rückgabewerte, die nicht verraten warum etwas fehlgeschlagen ist — ist der wichtigste Grund für die Existenz von Files. f.delete() gibt false zurück, wenn die Datei nicht vorhanden war, wenn keine Berechtigung bestand, wenn es ein nicht leeres Verzeichnis war oder wenn ein anderer Prozess sie unter Windows geöffnet hielt. Aus dem Rückgabewert lässt sich das nicht ablesen. Das entsprechende Files.delete(path) wirft eine spezifische Ausnahme (NoSuchFileException, AccessDeniedException, DirectoryNotEmptyException) und ist die API für echte Fehlerbehandlung.
renameTo ist der schlimmste Kandidat: es kann stillschweigend fehlschlagen, ohne eine Ausnahme zu werfen, und die Fehlermodi (Umbenennung über Volumes hinweg, Ziel existiert, Berechtigung, Sperre) hängen vom Betriebssystem ab. Files.move(src, dst, REPLACE_EXISTING) ist der moderne Ersatz und teilt mit, was schiefgelaufen ist.
Verbindung zu Path
Jede File-Instanz kennt ihren Path und umgekehrt:
File f = new File("data/users.txt");
Path p = f.toPath(); // bridge to java.nio.file
File g = p.toFile(); // bridge backBeide interoperieren günstig. Wenn man mit einer Legacy-API arbeitet, die File zurückgibt, ist die richtige Vorgehensweise meistens f.toPath() und dann Files.* darauf aufzurufen. Neuer Code sollte von Path.of(...) ausgehen und nur an der Aufrufstelle einer Legacy-Methode in File konvertieren.
Ein praktisches Beispiel: einen Verzeichnisbaum aufbauen und mit File durchlaufen
Das folgende Programm erstellt einen kleinen Verzeichnisbaum unter dem temporären Systemverzeichnis, füllt ihn mit einigen Dateien und fragt jeden Eintrag mit File ab. Es zeigt die FilenameFilter- und FileFilter-Lambdas, die Null-Rückgabe-Falle, die kanonische Pfadauflösung und das fehlende Fehlerinformationsproblem bei delete(). Alle Artefakte werden mit deleteOnExit bereinigt.
Was aus dem Programmlauf mitzunehmen ist:
a.getCanonicalPath()hat einen normalisierten absoluten Pfad ohne..-Segmente ausgegeben.getAbsolutePath()normalisiert nicht — für eine Sicherheitsprüfung ist die kanonische Variante die richtige, gegen die man ein erlaubtes Präfix vergleicht.- Die
FilenameFilter-Form(d, name) -> name.endsWith(".txt")ist ein zweiargumentiges Lambda;File::isFileist eine Methodenreferenz für das einargumentigeFileFilter. Beide sind funktionale Interfaces — dasselbe Vokabular wie in Teil 12 —Filewar „lambda-bereit", lange bevor Lambdas existierten. notDir.listFiles()hatnullzurückgegeben, weildata.csvkein Verzeichnis ist. Diefor-Schleife hätte eine NPE ausgelöst, wenn wir die Null-Prüfung übersprungen hätten.Files.list(path)wirft für denselben Fall eine klare Ausnahme.ghost.delete(),a.delete()undsub.delete()haben alle einenbooleanzurückgegeben. Die ersten beiden sind leicht zu lesen; der dritte gabfalsezurück, weil das Verzeichnis nicht leer war, und die API gab uns nichts, um „nicht leer" von „keine Berechtigung" zu unterscheiden. Diese Lücke schließtFiles.delete(path).root.toPath()ist die Brücke injava.nio.file. Sobald man einenPathhat, gilt der Rest von Teil 13 —Files.readString,Files.lines,Files.walkund all diestatic-Hilfsmethoden.
Was kommt als Nächstes
Das nächste Kapitel, Dateien in Java erstellen, behandelt die drei Möglichkeiten, eine neue Datei oder ein Verzeichnis anzulegen — das Legacy-File.createNewFile und mkdir(s) sowie das moderne Files.createFile, Files.createDirectory und Files.createDirectories — und zeigt, welche Variante für welchen Anwendungsfall geeignet ist.