Falha, ausência e resultado válido
Uma operação de leitura pode devolver texto vazio porque o ficheiro é válido e vazio, ou pode falhar antes de obter um resultado. Devolver a mesma String vazia nos dois casos elimina uma distinção de que o chamador pode precisar. Num serviço de configuração, vazio pode significar desativar regras; uma falha de acesso não deve provocar essa decisão por acidente.
IOException é uma exceção checked. Um método que chama Files.readString deve tratar essa falha ou declará-la no seu contrato. Acrescentar throws IOException permite propagá-la; não garante que a leitura vá funcionar. RuntimeException e Error não têm a mesma obrigação de declaração, embora continuem a poder interromper operações.
Escolhe o local de tratamento pela responsabilidade: a camada de leitura pode acrescentar contexto e preservar a causa, enquanto a camada que gere a versão ativa decide se mantém a configuração anterior. new IOException("Configuration read failed", cause) conserva a ligação ao erro original. Evita registar conteúdo sensível ou transformar o texto da exceção em dados de configuração. Os exemplos assumem os imports da API indicados no excerto.
Handlers e saídas que escondem falhas
Os catch são considerados pela ordem apresentada. Se IOException aparece antes de FileNotFoundException, o tratamento geral já abrange a subclasse e a ordem é rejeitada na compilação. Coloca o handler específico antes do geral quando a aplicação precisa de decisões diferentes. A decisão de repetir uma operação não deve depender apenas de a classe da exceção parecer recuperável: é preciso conhecer os efeitos já produzidos e o contrato da operação.
finally executa trabalho de saída quando o fluxo normal da linguagem abandona o try, mas também pode substituir o resultado anterior. Se o try lança IllegalStateException e finally faz return 7, a chamada devolve 7 e deixa de propagar aquela exceção. Um erro de limpeza lançado num finally manual pode igualmente esconder a falha original.
Uma política de diagnóstico deve evitar retornos de sucesso em finally. Mantém aí apenas a responsabilidade de limpeza adequada e conserva o resultado ou a exceção da operação. Isto não é uma promessa de execução perante terminação abrupta do processo ou perda de energia. Os exercícios analisam fluxo normal da JVM e falhas expressamente indicadas.
Aquisição, fecho e exceções suprimidas
try-with-resources declara os recursos cuja saída deve ser gerida. Os inicializadores são avaliados da esquerda para a direita; os recursos não nulos são fechados pela ordem inversa. Se abrir A e depois B, o fecho tenta B e depois A. Uma falha de fecho de B não impede tentar fechar A.
Se o corpo já lançou uma exceção, ela mantém-se principal; falhas posteriores de fecho são acrescentadas como exceções suprimidas, com a supressão normal ativa. getSuppressed permite consultar essa lista. Não a confundas com getCause: uma causa explica uma exceção que envolve outra, enquanto uma falha suprimida conserva um erro adicional ocorrido ao sair da mesma operação.
A gestão inclui a inicialização incompleta do conjunto de recursos. Se A abriu, mas openB falha, work não começa e A é fechado. Se esse fecho também falhar, a falha de openB permanece principal. O mecanismo não chama close numa variável B que não foi inicializada. Caso openB tenha adquirido recursos internamente antes de falhar, essa operação deve gerir a própria aquisição parcial. Ao rever um excerto, lista separadamente recursos adquiridos, corpo executado, fechos tentados e erro entregue ao chamador.
Leitura de ficheiros com um contrato explícito
Files.readString(path, StandardCharsets.UTF_8) especifica a interpretação dos bytes. Uma sequência malformada pode produzir IOException; esta API não promete reparar o ficheiro silenciosamente. A leitura integral é adequada para os pequenos ficheiros delimitados destes exemplos. Para entradas grandes ou sem limite conhecido, escolhe um desenho que controle memória e consumo progressivo, como um reader gerido por try-with-resources, com limites apropriados ao domínio.
A existência do caminho é outra questão. Files.exists é uma observação que pode ficar desatualizada logo depois da chamada. true não garante que o processo consiga ler, nem que outro participante não remova ou substitua o ficheiro antes da abertura. false também pode significar que a existência não pôde ser determinada. Não uses a verificação prévia para dispensar o tratamento da própria leitura.
Na operação, regista a etapa da falha sem adivinhar a causa. “Existe, mas não consegui ler” não demonstra apagamento, permissões ou bytes inválidos por si só. O tipo da exceção, a causa e o contexto da operação ajudam a distinguir essas hipóteses. Preserva-os sem expor o conteúdo lido.
Um Path não é uma autorização de acesso
Path representa um caminho segundo um sistema de ficheiros. resolve não obriga o argumento a permanecer sob a base: em Unix, resolver /tmp/input.txt contra /srv/import devolve o caminho absoluto /tmp/input.txt. normalize trata componentes redundantes lexicalmente. Não prova que o ficheiro existe e não segue ligações simbólicas.
Mesmo um resultado que passa startsWith(base) pode conduzir a outro local na abertura. Se /srv/import/redirect aponta para /private, o caminho /srv/import/redirect/item.txt tem o prefixo esperado e pode abrir /private/item.txt. A pergunta testa a diferença entre a forma do caminho e o objeto alcançado, sem executar acessos.
Para desenhar uma fronteira de acesso, define entradas admitidas, política de ligações e quem pode alterar os diretórios envolvidos. toRealPath consulta o sistema e resolve ligações por omissão para um ficheiro existente, mas uma verificação seguida de abertura continua a exigir análise das alterações possíveis nesse intervalo. Não apresentes normalize ou startsWith isolados como uma fronteira de segurança completa. A proteção tem de corresponder à operação e ao ambiente controlado.
Publicar uma versão: capacidade e garantia
Uma publicação pode preparar um ficheiro temporário, fechar a escrita e depois tentar torná-lo visível como destino. O resultado pretendido é que leitores vejam uma versão completa. Files.move com ATOMIC_MOVE exige uma operação atómica do sistema de ficheiros; se ela não for possível, a API lança AtomicMoveNotSupportedException. Retirar essa opção e repetir muda a garantia da operação.
Há outro detalhe quando o destino já existe: com ATOMIC_MOVE, as restantes opções são ignoradas, e o contrato geral deixa à implementação a substituição do destino ou a falha. Acrescentar REPLACE_EXISTING não transforma essa combinação numa promessa universal. A aplicação que suporta vários providers precisa de confirmar a capacidade de que depende em cada ambiente relevante.
Se a exigência é conservar a versão anterior até poder substituir atomicamente, apagar primeiro o destino cria um intervalo proibido. A política deve reportar uma capacidade em falta sem publicar silenciosamente de outra forma. Este caso trata visibilidade da mudança, com um único escritor. Não demonstra durabilidade perante perda de energia nem autoriza repetir cegamente após qualquer IOException. Esses requisitos exigem evidência própria.
Revisão guiada: carregar sem publicar uma falha
O serviço de configuração tem três resultados distintos: leitura válida não vazia, leitura válida vazia e falha de leitura. No excerto, loadConfig devolve a String apenas após a leitura completa; em caso de IOException, acrescenta contexto e conserva a causa. Não faz a publicação. Assim, o chamador só segue para validação e atualização quando recebe um resultado normal.
Para uma falha de acesso, a decisão do serviço é manter a versão ativa e registar uma tentativa falhada. Para um ficheiro realmente vazio, aplica a política de domínio, que neste cenário permite desativar regras opcionais. Devolver a versão anterior como fallback pode manter o serviço operacional, mas não deve ser descrito como sucesso de uma nova leitura.
Ao escolher a resposta, acompanha quem decide, que estado pode mudar e que evidência fica disponível. Evita catch genéricos que convertem todas as falhas em vazio e finally que devolve sucesso. As perguntas usam seleção única ou múltipla e interpretação de excertos. Não é necessário abrir ficheiros nem executar programas para responder.
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
final class ConfigurationReader {
// Contract: a small, bounded UTF-8 configuration file.
static String loadConfig(Path path) throws IOException {
try {
return Files.readString(path, StandardCharsets.UTF_8);
} catch (IOException cause) {
throw new IOException("Configuration read failed", cause);
}
}
}
Uma leitura válida de zero bytes devolve "". Uma falha de I/O é propagada como IOException com a causa original. O chamador só publica após leitura e validação bem-sucedidas; o método não altera a configuração ativa.
Armadilhas comuns
Devolver sucesso em finally; perder erros de fecho; tratar exists como reserva; confundir normalize com contenção; retirar ATOMIC_MOVE sem rever o contrato.
Tópicos relacionados: Métodos, contratos e fluxo de controlo · Objetos, interfaces e records · Streams, lambdas e ausência · Módulos, entrega e diagnóstico
Limpeza não equivale a sucesso. Conserva o erro original, verifica a operação real e só promete as garantias suportadas pelo contrato e pelo ambiente.
Referência: JLS 25: Try, finally and resource management · Java SE 25; no preview features