W3docs

Classe File do Java

Represente caminhos do sistema de arquivos em Java com a classe legada java.io.File — exists, isFile, isDirectory, listFiles.

java.io.File é o tipo original "esta string é um caminho" do Java 1.0. A própria classe não realiza nenhuma I/O — ela não abre, lê ou grava dados — ela apenas nomeia uma localização no sistema de arquivos e oferece alguns métodos para consultar o sistema operacional sobre essa localização e executar operações de disparo único nela (exists, isDirectory, delete, renameTo, listFiles).

java.nio.file.Path (Java 7) é o substituto moderno e é o que o código novo deve usar, mas você encontrará File em todo código mais antigo que ~2012, e muitas APIs antigas ainda aceitam e retornam esse tipo. Este capítulo aborda o que ela faz, onde estão as costuras e como ela faz a ponte com Path.

Construção

Um File envolve uma string de caminho. Quatro construtores cobrem os casos comuns:

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 + child

O construtor não faz validação — passar um caminho sem sentido constrói um File tranquilamente; apenas quando você chama exists(), delete(), etc. o sistema operacional é envolvido.

Use o construtor de dois argumentos para "pai + nome" em vez de concatenação de strings. Ele escolhe o separador correto (/ no Unix, \ no Windows) e evita o bug em que o caminho do pai pode ou não terminar com um separador:

File good = new File(parentDir, "data.txt");         // separator handled for you
File bad  = new File(parentDir + "/data.txt");        // brittle: depends on parentDir's exact string

Consultando o sistema de arquivos

File expõe um amplo conjunto de consultas que retornam boolean e long. As mais comuns:

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();

Essas chamadas cada uma atinge o sistema operacional. São baratas individualmente, mas não são gratuitas — chamar exists(), depois isDirectory() e depois length() são três syscalls. Se você precisar de vários atributos de um arquivo, Files.readAttributes(path, BasicFileAttributes.class) (próxima parte) é uma syscall em vez de várias.

Visualizações de caminho

File oferece várias maneiras de visualizar a mesma string subjacente:

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 IOException

getAbsolutePath e getCanonicalPath são o par mais confuso da classe:

  • getAbsolutePath — prefixa o diretório de trabalho atual da JVM se o caminho for relativo. Retorna a string com segmentos .. ainda presentes.
  • getCanonicalPath — igual ao absoluto, depois resolve .. e ., depois segue links simbólicos. Pode acessar o disco e lançar IOException.

Para verificações sensíveis à segurança (este caminho fornecido pelo usuário está dentro do diretório permitido?), getCanonicalPath é o único seguro — caso contrário, um caminho relativo como safe-dir/../../../etc/passwd passa por uma verificação startsWith("safe-dir").

Listando um diretório

Quatro variantes, dois pares:

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);                            // FileFilter

Tanto FilenameFilter quanto FileFilter são interfaces funcionais de método único (vocabulário da Parte 12), portanto uma lambda ou referência de método funciona diretamente. A diferença: FilenameFilter recebe o diretório pai e o nome simples; FileFilter recebe o File filho construído. Use FileFilter se precisar chamar isDirectory() ou length() para decidir; use FilenameFilter se a correspondência por nome for suficiente.

Todos os quatro métodos retornam null se o caminho não for um diretório — eles não lançam exceção. Isso é uma fonte clássica de NPE:

for (File child : dir.listFiles()) { ... }                   // NPE if dir is not a directory!
File[] children = dir.listFiles();
if (children != null) for (File c : children) { ... }         // correct

O moderno Files.list(path) retorna um Stream<Path> vazio para um diretório inexistente ou lança uma NotDirectoryException clara. A API de File apenas retorna null e deixa você travar.

Criando, excluindo, renomeando

File expõe alguns métodos de mutação:

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 boolean

O tema recorrente — valores de retorno boolean que não dizem por que — é a principal razão pela qual Files existe. f.delete() retorna false se o arquivo não existia, se você não tinha permissão, se era um diretório não vazio ou se outro processo o mantinha aberto no Windows. Você não pode saber qual é o motivo pelo valor de retorno. O correspondente Files.delete(path) lança uma exceção específica (NoSuchFileException, AccessDeniedException, DirectoryNotEmptyException) e é a API que você deseja para tratamento real de erros.

renameTo é o pior caso: pode falhar sem lançar nenhuma exceção, e os modos de falha (renomeação entre volumes, destino existe, permissão, bloqueio) dependem do sistema operacional. Files.move(src, dst, REPLACE_EXISTING) é o substituto moderno e informa o que deu errado.

Fazendo a ponte com Path

Todo File conhece seu Path e vice-versa:

File f = new File("data/users.txt");
Path p = f.toPath();              // bridge to java.nio.file
File g = p.toFile();              // bridge back

Os dois interoperam de forma barata. Quando você está preso com uma API legada que retorna File, o caminho certo geralmente é f.toPath() e depois chamar Files.* nele. O código novo deve começar a partir de Path.of(...) e só converter para File no ponto de chamada de um método legado.

Um exemplo prático: construindo e percorrendo uma árvore com File

O programa abaixo cria uma pequena árvore de diretórios no diretório temporário do sistema, a popula com alguns arquivos e depois consulta cada entrada com File. Ele demonstra as lambdas FilenameFilter e FileFilter, a armadilha do retorno null, a resolução do caminho canônico e o problema de informação de erro ausente com delete(). Todos os artefatos são limpos com deleteOnExit.

java— editable, runs on the server

O que observar na execução:

  • a.getCanonicalPath() imprimiu um caminho absoluto normalizado sem segmentos ... getAbsolutePath() não normaliza — para uma verificação de segurança, o caminho canônico é a versão que você compara com um prefixo permitido.
  • A forma FilenameFilter (d, name) -> name.endsWith(".txt") é uma lambda de dois argumentos; File::isFile é uma referência de método para o FileFilter de um argumento. Ambas são interfaces funcionais, o mesmo vocabulário da Parte 12 — File é "compatível com lambda" desde muito antes das lambdas existirem.
  • notDir.listFiles() retornou null porque data.csv não é um diretório. O loop for teria lançado NPE se tivéssemos ignorado a verificação de null. Files.list(path) lança uma exceção clara para o mesmo caso.
  • ghost.delete(), a.delete() e sub.delete() retornaram um boolean. Os dois primeiros são fáceis de interpretar; o terceiro retornou false porque o diretório não estava vazio, e a API não nos deu nada para distinguir "não estava vazio" de "sem permissão." Essa é a lacuna que Files.delete(path) fecha.
  • root.toPath() é a ponte para java.nio.file. Uma vez que você tem um Path, o restante da Parte 13 se aplica — Files.readString, Files.lines, Files.walk, todos os auxiliares static.

O que vem a seguir

O próximo capítulo, Criando Arquivos em Java, aborda as três formas de criar um novo arquivo ou diretório — o legado File.createNewFile e mkdir(s), além dos modernos Files.createFile, Files.createDirectory e Files.createDirectories — e qual escolher para cada situação.

Prática

Prática
`dir.listFiles()` em um `File` que aponta para um arquivo regular (não um diretório) retorna…
`dir.listFiles()` em um `File` que aponta para um arquivo regular (não um diretório) retorna…
Was this page helpful?