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 + childO 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 stringConsultando 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 IOExceptiongetAbsolutePath 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çarIOException.
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); // FileFilterTanto 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) { ... } // correctO 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 booleanO 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 backOs 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.
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 oFileFilterde 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()retornounullporquedata.csvnão é um diretório. O loopforteria 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()esub.delete()retornaram umboolean. Os dois primeiros são fáceis de interpretar; o terceiro retornoufalseporque 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 queFiles.delete(path)fecha.root.toPath()é a ponte parajava.nio.file. Uma vez que você tem umPath, o restante da Parte 13 se aplica —Files.readString,Files.lines,Files.walk, todos os auxiliaresstatic.
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.