Excluindo Arquivos em Java
Exclua arquivos e diretórios em Java com File.delete, Files.delete e Files.deleteIfExists.
Três chamadas simples, uma grande diferença. File.delete() retorna um boolean para tudo, desde sucesso até permissão negada; Files.delete() lança exceções específicas para cada falha; Files.deleteIfExists() é a opção intermediária — boolean para a pergunta "eu deletei algo?" apenas, exceções para falhas reais. Escolher a certa depende principalmente de quanto você se importa com por que a exclusão falhou.
Também abordado: remover uma árvore de diretório não vazia (nenhuma das três chamadas faz isso sozinha), a opção de abertura DELETE_ON_CLOSE para arquivos temporários, e o padrão seguro de "mover e depois excluir" para substituir um arquivo atomicamente.
File.delete() — legado, retorna boolean
File f = new File("notes.txt");
boolean ok = f.delete(); // true on success
// false on every failure: missing, locked, perms, non-empty dirA lacuna do legado, mesma forma que mkdir e renameTo: um único boolean para cada resultado. A chamada retorna false se o arquivo não existia, se você não tinha permissão, se o caminho era um diretório não vazio ou — no Windows — se outro processo mantinha o arquivo aberto. Você não consegue distinguir qual situação ocorreu pelo valor de retorno.
File.delete() remove:
- Um arquivo regular.
- Um diretório vazio. Diretórios não vazios retornam
false. - Um link simbólico em si (não o destino).
Se você precisa apenas de "o arquivo não existe mais ao final da chamada," verifique f.exists() depois em vez de depender do retorno:
f.delete();
if (f.exists()) throw new IOException("could not delete " + f);Esse padrão é o que você encontrará substituindo o método na maioria das bases de código mais antigas.
Files.delete(path) — moderno, lança exceções
O equivalente em java.nio.file troca o boolean por exceções específicas:
Path p = Path.of("notes.txt");
Files.delete(p);
// throws NoSuchFileException — the file didn't exist
// throws DirectoryNotEmptyException — path was a non-empty directory
// throws AccessDeniedException — permission denied
// throws IOException — anything else (locked, OS error, network FS hiccup)Os tipos de exceção são subclasses de IOException, então um catch (IOException e) amplo ainda funciona. A diferença é que o tratamento de erros real pode ser específico:
try {
Files.delete(path);
} catch (NoSuchFileException e) {
// already gone — not an error in the "delete if there" pattern
} catch (DirectoryNotEmptyException e) {
// need a recursive delete; handled below
}Se "já foi embora" está tudo bem, use Files.deleteIfExists em vez de capturar NoSuchFileException.
Files.deleteIfExists(path) — a opção intermediária
boolean deleted = Files.deleteIfExists(path);
// returns true — the file existed and was deleted
// returns false — the file did not exist
// throws DirectoryNotEmptyException, AccessDeniedException, etc. for real failuresO boolean aqui apenas distingue "eu removi algo" de "nada para remover." Erros reais ainda lançam exceções. É a chamada que você quer para código de setUp / tearDown, limpeza idempotente e padrões de "excluir este marcador antigo se estiver lá":
Files.deleteIfExists(Path.of("lock")); // safe whether the lock was there or notUse o quadro:
| Você quer… | Use |
|---|---|
| Boolean legado, sem informação de erro | File.delete() |
| "Excluir ou me diga por que falhou" | Files.delete(path) |
| "Excluir se existir; silencioso se não" | Files.deleteIfExists(path) |
Excluindo uma árvore de diretório não vazia
Nenhuma das chamadas de exclusão única remove um diretório não vazio. Existem dois padrões padrão:
Padrão 1: percorrer e excluir em ordem reversa. Files.walk produz um Stream<Path> em ordem arbitrária; ordenar em ordem reversa empurra as folhas para frente, de modo que cada pai está vazio quando você chega a ele.
try (Stream<Path> walk = Files.walk(root)) {
walk.sorted(Comparator.reverseOrder())
.forEach(p -> {
try { Files.delete(p); } catch (IOException e) { throw new UncheckedIOException(e); }
});
}Conciso e a versão que a maioria do código usa hoje. A desvantagem: ele carrega todos os caminhos na ordenação. Para diretórios com milhões de entradas, prefira o padrão 2.
Padrão 2: Files.walkFileTree com um SimpleFileVisitor. O padrão visitor permite que você exclua folhas na visita e pais em postVisitDirectory, sem necessidade de ordenação:
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override public FileVisitResult visitFile(Path file, BasicFileAttributes a) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});Mesmo resultado, sem ordenação em memória, mais linhas. A API visitor é abordada no capítulo Walk file tree.
Não existe rmrf embutido no JDK. Ambos os padrões acima são os substitutos padrão; muitas bases de código incluem um pequeno helper Files.deleteRecursively(root) em cima de um deles.
DELETE_ON_CLOSE — arquivos temporários que se limpam sozinhos
Para "preciso de um arquivo apenas enquanto este stream estiver aberto," a opção StandardOpenOption.DELETE_ON_CLOSE exclui o arquivo quando o stream fecha — mesmo no caminho de exceção:
Path scratch = Files.createTempFile("work-", ".tmp");
try (BufferedWriter w = Files.newBufferedWriter(scratch,
StandardOpenOption.WRITE, StandardOpenOption.DELETE_ON_CLOSE)) {
// ... write to and read from scratch ...
} // scratch is gone after this brace, regardless of how we got hereO arquivo é desvinculado do diretório imediatamente na maioria dos sistemas Unix (outros processos não podem mais vê-lo pelo nome; apenas o handle o mantém vivo). Esse é o padrão certo para dados temporários de curta duração — sem necessidade de lembrar o encanamento de try/finally.
File.deleteOnExit() é a versão mais antiga e mais fraca: ela enfileira uma exclusão para ser executada durante o encerramento da JVM. Não é chamada em kill -9 ou em uma falha da JVM, portanto vaza. Use DELETE_ON_CLOSE quando o tempo de vida do arquivo estiver vinculado a um stream; use um try/finally (ou try-with-resources em torno de um holder AutoCloseable) quando não estiver.
Uma nota sobre "substituição atômica"
Substituir um arquivo por outro atomicamente — de modo que um leitor nunca veja uma versão meio escrita — não é um padrão de excluir-e-escrever. O idioma padrão é "escrever em um arquivo irmão, depois renomear atomicamente":
Path target = Path.of("data.json");
Path tmp = target.resolveSibling("data.json.tmp");
Files.writeString(tmp, payload);
Files.move(tmp, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);ATOMIC_MOVE troca os dois caminhos em uma única etapa do SO (em sistemas de arquivos que suportam isso). O data.json antigo é substituído; não existe nenhum momento intermediário onde o arquivo está meio escrito ou ausente.
Um exemplo prático: cada método de exclusão e uma desmontagem recursiva
O programa abaixo constrói uma pequena árvore e, em seguida, exercita cada método de exclusão por vez — o boolean legado, a versão moderna que lança exceções, o intermediário "se existir" e, finalmente, o padrão Files.walk + reverseOrder que remove uma árvore inteira. Cada etapa imprime o que aconteceu.
O que tirar da execução:
- As duas chamadas
File.delete()ema.txtretornaramtruee depoisfalse. O segundofalseparece idêntico a uma falha de permissão — essa é a lacuna do legado. Files.delete(sub)lançouDirectoryNotEmptyExceptioneFiles.delete(missing)lançouNoSuchFileException. Duas subclasses específicas deIOException, dois modos de falha distintos — exatamente o que a API boolean não consegue informar.Files.deleteIfExists(b)retornoutruena primeira vez efalsena segunda. Esse segundofalseé apenas "não estava lá" — uma falha real (permissão negada, bloqueio) teria lançado uma exceção.- O bloco
Files.walk + reverseOrderexcluiu as folhas primeiro e os pais por último. Cada chamadaFiles.deleteao longo do caminho foi bem-sucedida porque, quando o visitor chegou a um diretório, seus filhos já haviam sido removidos. - O arquivo
DELETE_ON_CLOSEhavia desaparecido quando o writer fechou — garantido mesmo no caminho de exceção. (Na maioria dos sistemas Unix ele é desvinculado do diretório imediatamente na abertura, entãoFiles.existspode já reportarfalsedentro dotry; no Windows ele sobrevive até o handle fechar. De qualquer forma, nada é deixado para trás depois.) Esse é o padrão de arquivo temporário mais limpo no JDK — sem shutdown hook, semtry/finallypara lembrar.
O que vem a seguir
Isso encerra os capítulos de alto nível sobre "fazer uma coisa a um arquivo". O próximo capítulo, Byte Streams em Java, desce uma camada: InputStream e OutputStream, a abstração bruta orientada a bytes sobre a qual todos os arquivos, sockets e pipes em java.io são construídos. Muitos dos helpers que você usou até agora — Files.readString, Files.newBufferedWriter, até FileReader — são decoradores sobre essas duas interfaces.