Criando Arquivos em Java
Crie arquivos e diretórios em Java com File.createNewFile, Files.createFile e Files.createDirectories.
"Criar um novo arquivo vazio" e "criar esta árvore de diretórios" são duas das operações mais simples que o sistema de arquivos oferece, mas o Java expõe quatro maneiras sobrepostas de fazer cada uma. As diferenças importam — é a diferença entre "falha se o arquivo existe" e "sobrescreve silenciosamente," entre "mkdir" e "mkdir -p," entre um método legado que retorna boolean e um moderno que lança exceção.
Este capítulo cobre os quatro criadores:
File.createNewFile()— criação legada de arquivo.File.mkdir()/File.mkdirs()— criação legada de diretório.Files.createFile(path)— "criar ou falhar" atômico moderno.Files.createDirectory(path)/Files.createDirectories(path)— criação moderna de diretório.
Além dos auxiliares de arquivo temporário (Files.createTempFile, Files.createTempDirectory) e os flags OpenOption que permitem que escritores criem arquivos implicitamente.
File.createNewFile() — legado, retorna um boolean
File f = new File("data/users.txt");
boolean created = f.createNewFile(); // true if it actually created the file
// false if it already existed
// throws IOException if the parent dir is missingO contrato é verificação e criação atômica: o SO garante que nenhum outro processo pode criar o mesmo caminho entre a verificação de existência e a criação. Isso torna createNewFile um bloqueio primitivo em alguns idiomas legados de "arquivo PID" — if (!f.createNewFile()) throw new IllegalStateException("already running");
O que ele não faz:
- Ele não cria diretórios pai ausentes.
new File("does/not/exist/file.txt").createNewFile()lançaIOException. - Ele retorna
false(não lança) quando o arquivo já existe.
Se você só precisa que o arquivo exista ao final da chamada, o valor de retorno false está correto. Se você precisa que o arquivo seja totalmente novo (semântica de bloqueio), false é o sinal para tomar um caminho diferente.
File.mkdir() e File.mkdirs()
new File("logs").mkdir(); // creates "logs" — fails if "." has no perms or parent missing
new File("a/b/c").mkdirs(); // creates "a", "a/b", and "a/b/c" — like `mkdir -p`Ambos retornam boolean, ambos perdem informação sobre por que uma falha aconteceu. mkdir falha se um pai estiver ausente; mkdirs não. Ambos têm sucesso (retornam true) apenas se o diretório foi recém-criado — se já existe, retornam false. Combinado com o problema de "sem informação de erro," esta é a API legada que acaba sendo encapsulada em um auxiliar:
File dir = new File("data");
if (!dir.exists() && !dir.mkdirs()) throw new IOException("cannot create " + dir);O moderno Files.createDirectories(path) é o substituto de uma linha.
Files.createFile(path) — moderno, lança exceção
Files.createFile é o par java.nio.file de File.createNewFile() com uma diferença importante: ele lança exceção em vez de retornar um boolean.
Path p = Path.of("data/users.txt");
Files.createFile(p); // creates an empty regular file
// throws FileAlreadyExistsException if it exists
// throws NoSuchFileException if the parent is missingFileAlreadyExistsException é o que você captura com catch se o resultado de existência importa; NoSuchFileException é o que você captura (ou previne com createDirectories) se o pai pode não estar lá. Os tipos de exceção são subclasses específicas de IOException, então um catch (IOException e) genérico ainda funciona.
Você pode passar argumentos FileAttribute para definir permissões POSIX no momento da criação no Unix — o uso mais comum é garantir que arquivos secretos (chaves privadas, tokens) sejam criados com 0600:
import static java.nio.file.attribute.PosixFilePermissions.*;
var attr = asFileAttribute(fromString("rw-------"));
Files.createFile(Path.of("/tmp/secret"), attr); // born with 0600 permissions, atomically(Essa chamada lança UnsupportedOperationException no Windows, que não possui modelo de permissão POSIX — proteja com uma verificação de plataforma se você tiver como alvo ambos.)
Files.createDirectory versus Files.createDirectories
A mesma diferença que mkdir versus mkdir -p, apenas baseada em exceção:
Files.createDirectory(Path.of("logs")); // one level deep; parent must exist
Files.createDirectories(Path.of("a/b/c")); // creates every missing ancestorcreateDirectory lança FileAlreadyExistsException se o alvo já existe e não é um diretório; se já é um diretório, também lança (não é o que você geralmente quer).
createDirectories é a escolha mais amigável: não faz nada se todos os diretórios já estiverem lá, e cria o que estiver faltando caso contrário. Ele não lança se o caminho já existe como diretório. Isso o torna idempotente — seguro de chamar na inicialização sem uma verificação exists().
Arquivos e diretórios temporários
Para testes, espaço de trabalho e "preciso de um lugar seguro para colocar isso por alguns minutos," o JDK fornece Files.createTempFile e Files.createTempDirectory:
Path scratch = Files.createTempFile("session-", ".log"); // /tmp/session-3829387.log
Path workdir = Files.createTempDirectory("export-"); // /tmp/export-1827392Ambos escolhem um nome único no diretório temporário do sistema, ambos retornam um Path para a nova entrada, e ambos criam a entrada com permissões restritivas no Unix. O prefixo e o sufixo são dicas às quais o JDK acrescenta um valor único — você não pode escolher o nome exato (esse é o ponto: outro chamador não pode prevê-lo e sobrescrever o seu).
Arquivos temporários não são excluídos automaticamente. Você pode:
- Chamar
Files.deleteIfExists(path)quando terminar; ou - Chamar
path.toFile().deleteOnExit()para agendar uma exclusão no encerramento da JVM (cancelado por finalizações forçadas); ou - Abrir o arquivo com
StandardOpenOption.DELETE_ON_CLOSEse você só precisar dele enquanto um stream estiver aberto.
Escritores criam arquivos implicitamente
Na maioria das vezes você não precisa de uma chamada de "criar arquivo" — um escritor cria um para você. Files.newBufferedWriter, Files.write e Files.writeString aceitam varargs OpenOption... que decidem o que acontece quando o arquivo existe ou não:
import static java.nio.file.StandardOpenOption.*;
Files.writeString(path, "hello\n", CREATE, WRITE, TRUNCATE_EXISTING);
Files.writeString(path, "more\n", CREATE, WRITE, APPEND);
Files.writeString(path, "new\n", CREATE_NEW); // fails if file existsCREATE— criar se ausente, caso contrário abrir o existente.CREATE_NEW— criar, lançarFileAlreadyExistsExceptionse existir. A mesma semântica queFiles.createFile.TRUNCATE_EXISTING— limpar o conteúdo do arquivo ao abrir (o padrão parawriteStringquando não é append).APPEND— escrever no final sem truncar.
O padrão para Files.writeString (sem opções) é CREATE, WRITE, TRUNCATE_EXISTING — ou seja, "criar ou sobrescrever." Files.newBufferedWriter tem o mesmo padrão. Se você quer semântica de append, deve especificá-la explicitamente.
Um exemplo prático: cada criador lado a lado
O programa abaixo constrói uma pequena árvore do zero no diretório temporário do sistema usando ambas as APIs e várias opções abertas. Cada passo imprime o que mudou; o último bloco mostra o que acontece quando você re-executa uma operação em um caminho que já existe.
O que observar na execução:
- O primeiro
legacy.createNewFile()retornoutrue(criado); o segundo retornoufalse(já existia). Obooleannão diz o que aconteceu — você tem que lembrar a convenção. deep.mkdirs()teve sucesso uma vez e retornoufalsena segunda vez. Essefalseparece idêntico a "permissão negada" ou "pai ausente" — exatamente o problema de informação de erro faltante queFilesresolve.Files.createFileem um caminho existente lançouFileAlreadyExistsException. O tipo de exceção é específico, então um handler real pode distinguir "já existe" de "permissão negada" sem analisar strings.Files.createDirectorieschamado duas vezes seguidas não fez nada prejudicial na segunda vez. Essa é a propriedade que o torna a escolha certa em código de inicialização: sem guarda, apenas chame-o.Files.writeString(log, "line 1\n")criou o arquivo porqueCREATEestá nas opções padrão. A segunda e terceira chamadas usaramAPPENDexplicitamente, e o arquivo acumulou três linhas. A quarta chamada usouCREATE_NEWe recusou sobrescrever. Os padrões são projetados para o caso de "sobrescrever com novo conteúdo"; você opta pelo append.Files.createTempFile(root, "scratch-", ".tmp")produziu um nome comoscratch-1827392.tmp— seu prefixo e sufixo, mais um trecho único que a JVM escolhe para que duas chamadas concorrentes nunca colidam.- A limpeza percorre
rootem ordem inversa para que arquivos e diretórios filhos desapareçam antes de seus pais.Files.deleterecusa excluir um diretório não vazio; essa ordenação é como umrm -rfmanual é construído.
O que vem a seguir
Você pode criar arquivos; o próximo capítulo, Lendo Arquivos em Java, os lê — primeiro com os one-liners modernos (Files.readString, Files.readAllLines, Files.lines), depois com a pilha clássica de decoradores FileReader / BufferedReader / Scanner para que o código mais antigo nos próximos capítulos tenha uma base.