Classe Java NIO Path
Représentez les chemins du système de fichiers en Java moderne avec java.nio.file.Path et la fabrique Paths.
Path est le remplaçant moderne de java.io.File. Il représente un chemin dans le système de fichiers — une séquence ordonnée de composants de nom, éventuellement ancrée à / ou C:\ — et rien d'autre. Il ne lit pas le fichier. Il ne vérifie pas que le fichier existe. Il ne verrouille rien. Les opérations sur les octets du disque se trouvent dans Files (le chapitre suivant). Path est le nom ; Files est le verbe.
Si vous avez utilisé java.io.File, la différence est double : Path est immuable (chaque opération retourne un nouveau Path), et il sépare clairement « la chaîne du chemin » de « ce qui se trouve sur le disque à ce chemin ». La plupart des API modernes — Files, FileChannel, les surcharges de BufferedReader.lines() — prennent un Path, pas un File. Le nouveau code utilise Path.
Construire un Path
Path p = Path.of("logs", "2025", "app.log"); // joins components with the platform separator
Path q = Path.of("/etc/hosts"); // absolute Unix path
Path r = Paths.get("C:", "Users", "vaz"); // older factory — same behaviour
Path s = Path.of(URI.create("file:///etc/hosts")); // from a URIPath.of(...) est la fabrique moderne ; Paths.get(...) est l'ancienne et fonctionne toujours. Les deux construisent un objet de chemin sans toucher au système de fichiers. Path.of("nope/nope/nope") réussit même si ce fichier n'existe pas.
Path.of joint les varargs avec le File.separator de la plateforme — / sous Unix, \ sous Windows. Cela rend le chemin littéral que vous écrivez portable : Path.of("src", "main", "java") produit le bon résultat sur les deux plateformes. Dès que vous écrivez Path.of("src/main/java") avec des barres obliques codées en dur, vous l'avez rendu uniquement Unix par accident.
Inspecter un chemin
Les méthodes d'accès aux composants, sur Path.of("/var/log/app/today.log") :
| Méthode | Retourne | Exemple |
|---|---|---|
getFileName() | dernier composant en tant que Path | today.log |
getParent() | tout sauf le dernier | /var/log/app |
getRoot() | la racine, ou null si relatif | / |
getNameCount() | nombre de composants de nom | 4 |
getName(int i) | i-ème composant | getName(0) → var |
subpath(b, e) | composants de nom [b, e) | subpath(1, 3) → log/app |
isAbsolute() | si le chemin a une racine | true |
toString() | la chaîne formatée selon la plateforme | /var/log/app/today.log |
Ces méthodes sont pures : elles examinent la liste interne de noms de l'objet Path et en retournent des tranches. Aucune d'elles ne touche le disque.
resolve, resolveSibling et le piège de l'argument absolu
resolve(other) signifie « joindre this et other » :
Path base = Path.of("/var/log");
base.resolve("app.log"); // /var/log/app.log
base.resolve("app/today.log"); // /var/log/app/today.logLe piège : si other est absolu, resolve retourne other inchangé :
base.resolve("/etc/hosts"); // /etc/hosts -- base is discarded
base.resolve(Path.of("/etc/hosts")); // same: /etc/hostsC'est le comportement documenté, et il piège tous les programmeurs Java une fois. Si vous acceptez un nom de fichier d'une entrée utilisateur et le résolvez contre un répertoire de base configuré, un attaquant qui fournit "/etc/passwd" obtient son chemin absolu — échappant à la base. Validez ou normalisez toujours les entrées externes avant de les resolve-r.
resolveSibling(other) remplace le dernier composant :
Path p = Path.of("/var/log/today.log");
p.resolveSibling("yesterday.log"); // /var/log/yesterday.logC'est getParent().resolve(other) avec une vérification null intégrée. Utile pour « écrire la sortie à côté de l'entrée ».
relativize : l'inverse
Étant donné une base et une cible, base.relativize(target) retourne le chemin relatif de base vers target :
Path base = Path.of("/var/log");
Path target = Path.of("/var/log/app/today.log");
base.relativize(target); // app/today.log
target.relativize(base); // ../..Le contrat : base.resolve(base.relativize(target)) est équivalent à target (modulo normalize). C'est ainsi que vous transformez une cible absolue en une référence courte et relative dans un répertoire de base — utile pour les lignes de log, les entrées d'archive et les URL.
Les deux chemins doivent être du même type (tous deux absolus ou tous deux relatifs) et doivent tous deux provenir du même FileSystem. Mélanger les deux lève une IllegalArgumentException.
normalize : réduire . et ..
Les objets Path autorisent les composants . et .. — Path.of("/var/log/../tmp") est un Path valide. normalize() les supprime syntaxiquement :
Path.of("/var/log/../tmp").normalize(); // /var/tmp
Path.of("./a/./b/../c").normalize(); // a/c« Syntaxiquement » est important : normalize travaille au niveau de la chaîne. Elle ne demande pas au système de fichiers si .. pointe vraiment là où les chaînes le suggèrent. Si /var/log est un lien symbolique vers /tmp/logs, alors sur le disque /var/log/.. est /tmp, pas /var. normalize() ne le sait pas — elle supprime simplement le ...
Quand vous avez besoin du vrai chemin sur le disque (liens symboliques résolus, .. interprété correctement), utilisez toRealPath(), qui est un appel qui touche le système de fichiers :
Path real = Path.of("/var/log/../tmp").toRealPath(); // resolves symlinks, throws if the file doesn't existPour les vérifications d'égalité de chemins et les comparaisons de chaînes, normalize() est ce qu'il vous faut. Pour « le nom canonique du fichier sur le disque maintenant », c'est toRealPath().
L'égalité est basée sur les chaînes
path1.equals(path2) compare les chemins en tant que chaînes (composant par composant). Elle ne normalise pas, ne résout pas les liens symboliques, ne vérifie pas le système de fichiers :
Path.of("/var/log").equals(Path.of("/var/log/.")); // false -- one has a trailing '.' component
Path.of("/var/log/.").equals(Path.of("/var/log/.").normalize()); // false -- normalize() dropped the '.'
Path.of("/var/log").equals(Path.of("/var/log").normalize()); // true -- already normalized, no change
Path.of("/var/log").equals(Path.of("/var/log")); // truePour comparer deux chemins comme « pointent-ils vers le même fichier », normalisez les deux et comparez, ou appelez Files.isSameFile(p1, p2) (appel touchant le système de fichiers, la seule vérification totalement correcte). Pour le tri et les clés de HashSet, l'égalité de chaînes est ce que Path vous offre ; c'est bien pour la plupart des usages mais ne signifie pas « même fichier sur le disque ».
Interopérabilité avec File
Path et File se convertissent dans les deux sens :
File f = Path.of("/etc/hosts").toFile();
Path p = new File("/etc/hosts").toPath();Vous en aurez besoin quand une ancienne API prend un File et une nouvelle API prend un Path (ou vice versa). Ne stockez pas les chemins en tant que File ; convertissez à la frontière de l'API et gardez Path dans votre code.
Un exemple complet : join, resolve, relativize, normalize
Le programme ci-dessous parcourt chaque opération Path introduite dans ce chapitre avec des chemins concrets. La sortie rend visible la différence entre resolve et resolveSibling, entre normalize et toRealPath, et entre un argument absolu et un argument relatif dans resolve.
Ce qu'il faut retenir de l'exécution :
Path.of(\"/var\", \"log\", \"app\", \"today.log\")a produit/var/log/app/today.logsous Unix et\\var\\log\\app\\today.logsous Windows. Laisser la fabrique varargs joindre les composants est la méthode portable ; les/ou\codés en dur dans la chaîne d'entrée sont la méthode non portable. Utilisez la fabrique.- La ligne
resolve(\"/etc/hosts\")a ignorébaseet retourné/etc/hosts. C'est le comportement de l'argument absolu et la source la plus fréquente du « mais j'ai donné un répertoire de base, pourquoi écrit-il dans/etc/hosts? ». Validez toujours les noms de fichiers fournis par l'utilisateur avantresolve. La forme défensive estbase.resolve(other).normalize().startsWith(base)— et même cela a des subtilités quand des liens symboliques sont impliqués. base.relativize(target)a retournéapp/today.log. Concatener ça avecbase.resolve(...)a produit la cible d'origine — l'identité aller-retour. Utilisez ceci quand vous écrivez un message de log ou une entrée d'archive qui nécessite une forme courte et relative d'un chemin absolu long.Path.of(\"/var/log/../tmp/./a/b/../c\").normalize()a produit/var/tmp/a/c. La transformation était purement au niveau de la chaîne : chaque.supprimé, chaque pairenom/..supprimée. Le système de fichiers n'a pas été consulté.toRealPathà la ligne suivante a consulté le système de fichiers — c'est pourquoi le résultat était le nom canonique résolu en liens symboliques du fichier réel sur le disque. (Sous macOS, vous verrez le chemin temporaire revenir ancré à/private/var/folders/...plutôt que/var/folders/...:toRealPatha suivi le vrai lien symbolique/var→/private/var, ce quenormalizene peut jamais faire.) CommetoRealPathvérifie chaque composant, le répertoire intermédiairesubdans l'exemple doit réellement exister — c'est pourquoi le programme le crée d'abord.- Les deux vérifications d'égalité :
Path.of(\"/var/log\")etPath.of(\"/var\", \"log\")étaient égaux (même séquence de noms interne, même chaîne) ;Path.of(\"/var/log\")etPath.of(\"/var/log/.\")ne l'étaient pas (l'un a un composant.final, l'autre non). La leçon :equalsest une comparaison de chaînes. Pour « ces deux chemins pointent-ils vers le même fichier ? », utilisezFiles.isSameFile(a, b)du chapitre suivant — c'est la seule vérification qui interroge le système de fichiers.
Et ensuite
Path est le nom. Le chapitre suivant, Java Files Class, couvre le verbe — Files, l'immense classe utilitaire d'opérations en une ligne sur le système de fichiers : readString, writeString, createDirectories, copy, move, delete, walk, et le reste.