W3docs

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 URI

Path.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éthodeRetourneExemple
getFileName()dernier composant en tant que Pathtoday.log
getParent()tout sauf le dernier/var/log/app
getRoot()la racine, ou null si relatif/
getNameCount()nombre de composants de nom4
getName(int i)i-ème composantgetName(0)var
subpath(b, e)composants de nom [b, e)subpath(1, 3)log/app
isAbsolute()si le chemin a une racinetrue
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.log

Le 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/hosts

C'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.log

C'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 exist

Pour 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"));              // true

Pour 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.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Path.of(\"/var\", \"log\", \"app\", \"today.log\") a produit /var/log/app/today.log sous Unix et \\var\\log\\app\\today.log sous 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é base et 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 avant resolve. La forme défensive est base.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 avec base.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 paire nom/.. 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/... : toRealPath a suivi le vrai lien symbolique /var/private/var, ce que normalize ne peut jamais faire.) Comme toRealPath vérifie chaque composant, le répertoire intermédiaire sub dans 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\") et Path.of(\"/var\", \"log\") étaient égaux (même séquence de noms interne, même chaîne) ; Path.of(\"/var/log\") et Path.of(\"/var/log/.\") ne l'étaient pas (l'un a un composant . final, l'autre non). La leçon : equals est une comparaison de chaînes. Pour « ces deux chemins pointent-ils vers le même fichier ? », utilisez Files.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.

Pratique

Pratique
`base` est `Path.of('/srv/uploads')` et `userInput` est la chaîne `'/etc/passwd'` (une valeur contrôlée par un attaquant). Votre code fait `base.resolve(userInput)` pour calculer le chemin cible. Quel est le chemin résultant, et quelle est la leçon de sécurité ?
`base` est `Path.of('/srv/uploads')` et `userInput` est la chaîne `'/etc/passwd'` (une valeur contrôlée par un attaquant). Votre code fait `base.resolve(userInput)` pour calculer le chemin cible. Quel est le chemin résultant, et quelle est la leçon de sécurité ?
Was this page helpful?