Classe Path do Java NIO
Represente caminhos do sistema de arquivos com java.nio.file.Path e a fábrica Paths no Java moderno.
Path é o substituto moderno para java.io.File. Ele representa um caminho no sistema de arquivos — uma sequência ordenada de componentes de nome, opcionalmente com raiz em / ou C:\ — e nada mais. Não lê o arquivo. Não verifica se o arquivo existe. Não bloqueia nada. As operações de bytes no disco estão em Files (o próximo capítulo). Path é o substantivo; Files é o verbo.
Se você já usou java.io.File, a diferença é dupla: Path é imutável (toda operação retorna um novo Path), e ele separa claramente "a string do caminho" de "o que está no disco naquele caminho." A maioria das APIs modernas — Files, FileChannel, sobrecargas de BufferedReader.lines() — aceita Path, não File. Código novo usa Path.
Construindo um 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(...) é a fábrica moderna; Paths.get(...) é a mais antiga e ainda funciona. Ambas constroem um objeto de caminho sem tocar no sistema de arquivos. Path.of("nope/nope/nope") tem sucesso mesmo que nenhum arquivo desse tipo exista.
Path.of une os varargs com o File.separator da plataforma — / no Unix, \ no Windows. Isso torna o caminho que você escreve portável: Path.of("src", "main", "java") constrói o resultado correto em ambas as plataformas. No momento em que você escreve Path.of("src/main/java") com barras codificadas, tornou-o acidentalmente exclusivo do Unix.
Inspecionando um caminho
Os métodos de acesso a componentes, em Path.of("/var/log/app/today.log"):
| Método | Retorna | Exemplo |
|---|---|---|
getFileName() | último componente como Path | today.log |
getParent() | tudo exceto o último | /var/log/app |
getRoot() | a raiz, ou null se relativo | / |
getNameCount() | número de componentes de nome | 4 |
getName(int i) | i-ésimo componente | getName(0) → var |
subpath(b, e) | componentes de nome [b, e) | subpath(1, 3) → log/app |
isAbsolute() | se o caminho tem uma raiz | true |
toString() | a string formatada pela plataforma | /var/log/app/today.log |
Esses métodos são puros: analisam a lista interna de nomes do objeto de caminho e retornam fatias dela. Nenhum deles toca o disco.
resolve, resolveSibling e a armadilha do argumento absoluto
resolve(other) é "juntar this e other":
Path base = Path.of("/var/log");
base.resolve("app.log"); // /var/log/app.log
base.resolve("app/today.log"); // /var/log/app/today.logA armadilha: se other for absoluto, resolve retorna other sem alterações:
base.resolve("/etc/hosts"); // /etc/hosts -- base is discarded
base.resolve(Path.of("/etc/hosts")); // same: /etc/hostsEsse é o comportamento documentado, e ele pega todo programador Java uma vez. Se você aceita um nome de arquivo da entrada do usuário e o resolve contra um diretório base configurado, um atacante que forneça "/etc/passwd" obtém seu caminho absoluto — escapando do base. Sempre valide ou normalize a entrada externa antes de fazer resolve.
resolveSibling(other) substitui o último componente:
Path p = Path.of("/var/log/today.log");
p.resolveSibling("yesterday.log"); // /var/log/yesterday.logÉ getParent().resolve(other) com uma verificação de null embutida. Útil para "escrever a saída ao lado da entrada."
relativize: o inverso
Dado um base e um alvo, base.relativize(target) retorna o caminho relativo de base para 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); // ../..O contrato: base.resolve(base.relativize(target)) é equivalente a target (módulo normalize). É assim que você converte um alvo absoluto em uma referência curta e relativa dentro de um diretório base — útil para linhas de log, entradas de arquivo e URLs.
Ambos os caminhos devem ser do mesmo tipo (ambos absolutos ou ambos relativos) e devem vir do mesmo FileSystem. Misturar lança IllegalArgumentException.
normalize: colapsar . e ..
Objetos Path permitem componentes . e .. — Path.of("/var/log/../tmp") é um Path válido. normalize() os remove sintaticamente:
Path.of("/var/log/../tmp").normalize(); // /var/tmp
Path.of("./a/./b/../c").normalize(); // a/c"Sintaticamente" importa: normalize faz trabalho no nível de string. Ele não pergunta ao sistema de arquivos se .. realmente aponta para onde as strings sugerem. Se /var/log é um link simbólico para /tmp/logs, então no disco /var/log/.. é /tmp, não /var. normalize() não sabe disso — ele apenas apaga o ...
Quando você precisa do caminho real no disco (links simbólicos resolvidos, .. interpretado corretamente), use toRealPath(), que é uma chamada que toca o sistema de arquivos:
Path real = Path.of("/var/log/../tmp").toRealPath(); // resolves symlinks, throws if the file doesn't existPara verificações de igualdade de caminhos e comparações de strings, normalize() é o que você quer. Para "o nome canônico do arquivo no disco agora," é toRealPath().
A igualdade é baseada em string
path1.equals(path2) compara os caminhos como strings (componente por componente). Não normaliza, não resolve links simbólicos, não verifica o sistema de arquivos:
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")); // truePara comparar dois caminhos como "eles apontam para o mesmo arquivo," normalize ambos e compare, ou chame Files.isSameFile(p1, p2) (que toca o sistema de arquivos, a única verificação totalmente correta). Para ordenação e chaves de HashSet, a igualdade de string é o que Path oferece; é adequada para a maioria dos usos, mas não é "mesmo arquivo no disco."
Interoperabilidade com File
Path e File se convertem em ambas as direções:
File f = Path.of("/etc/hosts").toFile();
Path p = new File("/etc/hosts").toPath();Você precisará disso quando uma API antiga aceita File e uma nova API aceita Path (ou vice-versa). Não armazene caminhos como File; converta na fronteira da API e mantenha Path no seu código.
Um exemplo prático: join, resolve, relativize, normalize
O programa abaixo percorre todas as operações de Path apresentadas neste capítulo com caminhos concretos. A saída torna visível a diferença entre resolve e resolveSibling, entre normalize e toRealPath, e entre resolve com argumento absoluto e com argumento relativo.
O que extrair da execução:
Path.of(\"/var\", \"log\", \"app\", \"today.log\")produziu/var/log/app/today.logno Unix e\\var\\log\\app\\today.logno Windows. Deixar a fábrica de varargs unir os componentes é a forma portável; barras ou contrabarras codificadas na string de entrada é a forma não portável. Use a fábrica.- A linha
resolve(\"/etc/hosts\")descartoubasee retornou/etc/hosts. Esse é o comportamento com argumento absoluto e a fonte mais frequente de "mas eu forneci um diretório base, por que está escrevendo em/etc/hosts?" Sempre valide nomes de arquivo fornecidos pelo usuário antes deresolve. A forma defensiva ébase.resolve(other).normalize().startsWith(base)— e mesmo isso tem armadilhas sutis quando links simbólicos estão envolvidos. base.relativize(target)retornouapp/today.log. Concatenar isso de volta combase.resolve(...)produziu o alvo original — a identidade de ida e volta. Use isso quando você está escrevendo uma mensagem de log ou uma entrada de arquivo que precisa de uma forma curta e relativa de um longo caminho absoluto.Path.of(\"/var/log/../tmp/./a/b/../c\").normalize()produziu/var/tmp/a/c. A transformação foi puramente no nível de string: cada.removido, cada parnome/..removido. O sistema de arquivos não foi consultado.toRealPathna linha seguinte consultou o sistema de arquivos — por isso o resultado foi o nome canônico, com links simbólicos resolvidos, do arquivo real no disco. (No macOS você verá o caminho temporário retornar com raiz em/private/var/folders/...em vez de/var/folders/...:toRealPathseguiu o link simbólico real/var→/private/var, algo quenormalizenunca pode fazer.) ComotoRealPathverifica cada componente, o diretório intermediáriosubno exemplo precisa realmente existir — é por isso que o programa o cria primeiro.- As duas verificações de
equals:Path.of(\"/var/log\")ePath.of(\"/var\", \"log\")foram iguais (mesma sequência interna de nomes, mesma string);Path.of(\"/var/log\")ePath.of(\"/var/log/.\")não foram (um tem um componente.no final, o outro não). A conclusão:equalsé uma comparação de strings. Para "esses dois caminhos apontam para o mesmo arquivo?" useFiles.isSameFile(a, b)do próximo capítulo — é a única verificação que pergunta ao sistema de arquivos.
O que vem a seguir
Path é o substantivo. O próximo capítulo, Classe Files do Java, cobre o verbo — Files, a enorme classe utilitária de operações de uma linha no sistema de arquivos: readString, writeString, createDirectories, copy, move, delete, walk e o restante.