La classe File en Java
Représentez des chemins de fichiers en Java avec java.io.File — exists, isFile, isDirectory, listFiles.
java.io.File est le type original "cette chaîne est un chemin" introduit dans Java 1.0. La classe elle-même n'effectue aucune E/S — elle n'ouvre, ne lit et n'écrit aucune donnée — elle nomme simplement un emplacement dans le système de fichiers et offre quelques méthodes pour interroger le système d'exploitation sur cet emplacement et pour y effectuer des opérations ponctuelles (exists, isDirectory, delete, renameTo, listFiles).
java.nio.file.Path (Java 7) est le remplaçant moderne et c'est ce que le nouveau code devrait utiliser, mais vous rencontrerez File dans chaque base de code antérieure à ~2012, et de nombreuses API plus anciennes continuent de l'accepter et de le retourner. Ce chapitre couvre ce qu'il fait, où se situent les limites, et comment il s'interface avec Path.
Construction
Un File encapsule une chaîne de chemin. Quatre constructeurs couvrent les cas courants :
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 + childLe constructeur n'effectue aucune validation — passer un chemin absurde construit un File sans problème ; ce n'est que lorsque vous appelez exists(), delete(), etc. que le système d'exploitation intervient.
Utilisez le constructeur à deux arguments pour "parent + nom" plutôt que la concaténation de chaînes. Il choisit le bon séparateur (/ sous Unix, \ sous Windows) et évite le bug où le chemin parent peut ou non se terminer par un séparateur :
File good = new File(parentDir, "data.txt"); // separator handled for you
File bad = new File(parentDir + "/data.txt"); // brittle: depends on parentDir's exact stringInterrogation du système de fichiers
File expose un large ensemble de requêtes retournant des valeurs boolean et long. Les plus courantes :
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();Chacun de ces appels sollicite le système d'exploitation. Ils sont peu coûteux individuellement mais pas gratuits — appeler exists() puis isDirectory() puis length() représente trois appels système. Si vous avez besoin de plusieurs attributs d'un même fichier, Files.readAttributes(path, BasicFileAttributes.class) (partie suivante) n'effectue qu'un seul appel système.
Vues du chemin
File vous offre plusieurs façons d'examiner la même chaîne sous-jacente :
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 et getCanonicalPath sont la paire la plus confusante de la classe :
getAbsolutePath— préfixe le répertoire de travail courant de la JVM si le chemin est relatif. Retourne la chaîne avec les segments..encore présents.getCanonicalPath— identique au chemin absolu, puis résout..et., puis suit les liens symboliques. Peut accéder au disque et lever uneIOException.
Pour les vérifications sensibles à la sécurité (ce chemin fourni par l'utilisateur est-il dans le répertoire autorisé ?), getCanonicalPath est la seule sûre — sinon un chemin relatif comme safe-dir/../../../etc/passwd passe au travers d'une vérification startsWith("safe-dir").
Listage d'un répertoire
Quatre variantes, deux paires :
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); // FileFilterFilenameFilter et FileFilter sont des interfaces fonctionnelles à une seule méthode (vocabulaire de la partie 12), donc une lambda ou une référence de méthode fonctionne directement. La différence : FilenameFilter reçoit le répertoire parent et le nom nu ; FileFilter reçoit le File enfant construit. Utilisez FileFilter si vous avez besoin d'appeler isDirectory() ou length() pour décider ; utilisez FilenameFilter si le filtrage par nom suffit.
Ces quatre méthodes retournent null si le chemin n'est pas un répertoire — elles ne lèvent pas d'exception. C'est une source classique de NullPointerException :
for (File child : dir.listFiles()) { ... } // NPE if dir is not a directory!
File[] children = dir.listFiles();
if (children != null) for (File c : children) { ... } // correctLa méthode moderne Files.list(path) retourne un Stream<Path> vide pour un répertoire manquant ou lève une NotDirectoryException claire. L'API File retourne simplement null et vous laisse planter.
Création, suppression, renommage
File expose quelques méthodes de mutation :
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 booleanLe thème récurrent — les valeurs de retour boolean qui n'expliquent pas pourquoi — est la principale raison d'être de Files. f.delete() retourne false si le fichier n'existait pas, si vous n'aviez pas la permission, s'il s'agissait d'un répertoire non vide, ou si un autre processus le maintenait ouvert sous Windows. Il est impossible de déterminer lequel à partir de la valeur de retour. La méthode correspondante Files.delete(path) lève une exception spécifique (NoSuchFileException, AccessDeniedException, DirectoryNotEmptyException) et constitue l'API appropriée pour une vraie gestion des erreurs.
renameTo est le pire offenseur : il peut échouer sans lever la moindre exception, et les modes d'échec (renommage entre volumes, cible existante, permission, verrou) dépendent du système d'exploitation. Files.move(src, dst, REPLACE_EXISTING) est le remplaçant moderne et vous informe de ce qui a mal tourné.
Interface avec Path
Chaque File connaît son Path et vice versa :
File f = new File("data/users.txt");
Path p = f.toPath(); // bridge to java.nio.file
File g = p.toFile(); // bridge backLes deux interopèrent à faible coût. Quand vous êtes bloqué par une API héritée qui retourne un File, la bonne approche est généralement f.toPath() puis d'appeler Files.* dessus. Le nouveau code devrait partir de Path.of(...) et ne convertir en File qu'au point d'appel d'une méthode héritée.
Exemple concret : construction d'une arborescence et parcours avec File
Le programme ci-dessous crée une petite arborescence de répertoires dans le répertoire temporaire du système, la peuple de quelques fichiers, puis interroge chaque entrée avec File. Il démontre les lambdas FilenameFilter et FileFilter, le piège du retour null, la résolution du chemin canonique, et le problème du manque d'informations sur les erreurs avec delete(). Chaque artefact est nettoyé avec deleteOnExit.
Ce qu'il faut retenir de l'exécution :
a.getCanonicalPath()affiche un chemin absolu normalisé sans segment...getAbsolutePath()ne normalise pas — pour une vérification de sécurité, c'est la version canonique que vous comparez à un préfixe autorisé.- La forme
FilenameFilter(d, name) -> name.endsWith(".txt")est une lambda à deux arguments ;File::isFileest une référence de méthode pour leFileFilterà un argument. Les deux sont des interfaces fonctionnelles, le même vocabulaire que la partie 12 —Fileétait "prêt pour les lambdas" bien avant que les lambdas n'existent. notDir.listFiles()a retournénullparce quedata.csvn'est pas un répertoire. La boucleforaurait levé une NPE si nous avions omis la vérification dunull.Files.list(path)lève une exception claire dans le même cas.ghost.delete(),a.delete()etsub.delete()ont tous retourné unboolean. Les deux premiers sont faciles à interpréter ; le troisième a retournéfalseparce que le répertoire n'était pas vide, et l'API ne nous a donné aucun moyen de distinguer "n'était pas vide" de "pas de permission". C'est le manque queFiles.delete(path)comble.root.toPath()est le pont versjava.nio.file. Une fois que vous avez unPath, le reste de la partie 13 s'applique —Files.readString,Files.lines,Files.walk, tous les assistantsstatic.
La suite
Le prochain chapitre, Création de fichiers en Java, couvre les trois façons de créer un nouveau fichier ou répertoire — l'héritage File.createNewFile et mkdir(s), ainsi que les modernes Files.createFile, Files.createDirectory et Files.createDirectories — et lequel choisir pour quelle tâche.