05 / 11 Curso de Java do zero | fundamentos e linguagem · Aula escrita · atualizado em 25 set 2026

O que fazer quando os dados não vêm como você esperava?

Um arquivo de cobranças contém duas linhas válidas, uma data impossível e um valor escrito como texto. Somar tudo como se nada tivesse acontecido produz um resultado errado. Parar sem explicar a causa também não ajuda quem enviou o arquivo. Precisamos distinguir falha de entrada, falha de leitura e uma operação válida.

Fernanda Kipper
Fernanda Kipper
Próxima aula →

Um arquivo de cobranças contém duas linhas válidas, uma data impossível e um valor escrito como texto. Somar tudo como se nada tivesse acontecido produz um resultado errado. Parar sem explicar a causa também não ajuda quem enviou o arquivo. Precisamos distinguir falha de entrada, falha de leitura e uma operação válida.

Você já deve conhecer métodos, classes e coleções. Usaremos Java 21. Vamos combinar exceções, leitura de arquivo, datas e valores decimais em um exemplo pequeno, sem banco ou Spring. Ao final, você conseguirá rejeitar uma linha informando seu número e impedir que uma falha de acesso seja confundida com um arquivo vazio.

Exceção muda o caminho da execução

Uma exceção é um objeto que sinaliza uma situação em que o caminho normal não continuou. throw lança esse objeto. A execução procura um catch compatível; se não existir naquele método, a falha pode se propagar ao chamador.

O stack trace registra o caminho de chamadas relacionado à falha. Leia o tipo, a mensagem, a causa quando houver e a primeira linha relevante do seu código. Não tente resolver tudo adicionando catch (Exception e) {}: esse bloco vazio pode transformar uma falha em aparente sucesso.

Exceções checked, como IOException, precisam ser capturadas ou declaradas com throws. Exceções derivadas de RuntimeException são unchecked: o compilador não exige essa declaração. Isso não diz se uma falha é importante; diz como a linguagem exige que ela seja declarada ou tratada. Error representa outra categoria de problemas e não deve ser capturado indiscriminadamente como parte da regra de negócio.

Tratar significa decidir o que fazer

Se uma linha tem data inválida, podemos informar o número e continuar, porque escolhemos importação parcial. Se o arquivo inteiro não pode ser lido, não temos como calcular um total confiável: precisamos avisar a falha. Em um sistema financeiro real, talvez a política seja rejeitar o lote inteiro. A política vem do domínio, não do try/catch.

Quando você converte uma falha para outra exceção, preserve sua causa, por exemplo new IllegalStateException("Falha ao importar", causa). A mensagem explica o contexto; a causa mantém o diagnóstico. Capturar e registrar a mesma falha em todas as camadas costuma gerar ruído duplicado.

Um arquivo é bytes; um charset dá significado ao texto

Path representa um caminho. Files oferece operações sobre arquivos. Um charset, como UTF-8, define como bytes são interpretados como caracteres. Declarar o charset ajuda a evitar que nomes acentuados dependam da configuração da máquina.

Um leitor mantém um recurso aberto. Try-with-resources chama close ao sair do bloco, inclusive quando uma exceção ocorre. Isso é diferente de esperar o coletor de lixo. Um recurso pode implementar AutoCloseable para participar desse mecanismo.

Data de vencimento não é instante global

LocalDate representa ano, mês e dia sem horário ou fuso: é adequado para um vencimento definido por data. Instant representa um ponto na linha do tempo, útil para registrar quando algo aconteceu. ZonedDateTime combina data/hora com uma zona, como America/Sao_Paulo.

Uma data não vira um instante sem uma decisão adicional sobre horário e zona. Também não trate “amanhã às nove” como simplesmente somar 24 horas em qualquer contexto: mudanças de fuso e calendário podem importar. Nosso arquivo usa datas ISO, como 2026-10-01, evitando ambiguidade entre dia e mês.

Dinheiro exige uma política de precisão

double usa representação binária e não representa exatamente todos os decimais. Para estes valores monetários, usamos BigDecimal construído de texto. new BigDecimal("0.1") representa o decimal escrito; construir a partir do double pode carregar sua aproximação.

BigDecimal é imutável: total.add(valor) devolve outro objeto. Se você ignorar o retorno, total não muda. compareTo compara o valor numérico; equals também considera escala, então 2.0 e 2.00 podem ser numericamente iguais sem serem iguais por equals.

A importação inteira, passo a passo

Crie cobrancas.txt ao lado do programa:

2026-10-01;10.20
2026-10-02;4.80
2026-02-30;9.00

Salve este código em Importacao.java:

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;

public class Importacao {
    public static void main(String[] args) {
        BigDecimal total = new BigDecimal("0.00");
        int validas = 0;
        int invalidas = 0;

        try (var leitor = Files.newBufferedReader(
                Path.of("cobrancas.txt"), StandardCharsets.UTF_8)) {
            String linha;
            int numero = 0;
            while ((linha = leitor.readLine()) != null) {
                numero++;
                try {
                    String[] campos = linha.split(";", -1);
                    if (campos.length != 2) {
                        throw new IllegalArgumentException("Esperadas duas colunas");
                    }
                    LocalDate.parse(campos[0].trim());
                    BigDecimal valor = new BigDecimal(campos[1].trim())
                            .setScale(2, RoundingMode.UNNECESSARY);
                    if (valor.signum() < 0) {
                        throw new IllegalArgumentException("Valor negativo");
                    }
                    total = total.add(valor);
                    validas++;
                } catch (IllegalArgumentException | DateTimeParseException
                         | ArithmeticException erro) {
                    invalidas++;
                    System.out.println("Linha " + numero + " inválida");
                }
            }
            System.out.println("Válidas: " + validas);
            System.out.println("Inválidas: " + invalidas);
            System.out.println("Total: " + total.toPlainString());
        } catch (IOException erro) {
            System.err.println("Importação incompleta: " + erro.getMessage());
        }
    }
}

A saída esperada informa linha 3 inválida, duas válidas, uma inválida e total 15.00. Não é um parser geral de CSV: o formato deste exercício tem duas colunas simples, sem campos escapados ou separadores dentro delas.

O -1 no split preserva uma coluna vazia no final, permitindo distingui-la de uma linha com formato diferente. setScale com UNNECESSARY rejeita valores que exigiriam arredondar para duas casas. signum() informa se o número é negativo, zero ou positivo. A data é validada, mas não armazenada neste relatório.

O catch interno cobre problemas conhecidos de conteúdo. O externo cobre abertura ou leitura. Se a leitura falha no meio, o programa não imprime o resumo como se o lote tivesse acabado normalmente. A mensagem ao usuário é curta; em uma aplicação real, mantenha detalhes técnicos em um canal de diagnóstico apropriado.

Arredondamento não cria uma regra de negócio sozinho

Dividir 10 por 3 exige decidir o destino do centavo restante. Arredondar cada parcela para 3,33 produz 9,99. Uma solução possível é duas parcelas de 3,33 e uma de 3,34; outra política precisa ser definida pelo produto. BigDecimal ajuda a expressar a decisão, mas não a escolhe.

Divisão inteira por zero lança exceção; divisão em ponto flutuante pode produzir infinito ou NaN. Não generalize um comportamento para todos os tipos numéricos.

Confira com uma variação

Acrescente 2026-10-03;2.345 ao arquivo. Ela deve ser rejeitada, pois a política não permite arredondamento implícito. Acrescente depois 2026-10-03;2.30: o total deve virar 17,30 com três linhas válidas. Se desapareceu o centavo, veja se usou double ou ignorou o retorno de add.

Em outra sessão, explique por que um arquivo ausente não equivale a um arquivo vazio e por que LocalDate não contém fuso. Depois altere a política para rejeitar qualquer lote com linha inválida: calcule primeiro, confirme depois. Essa separação prepara a conversa sobre testes e transações.

Referência usada: guia de exceções e recursos da biblioteca padrão.

Pratique com /aprender

Copie o comando e cole no chat para iniciar uma prática guiada:

/aprender Pratique exceções em Java 21 usando uma linha de arquivo com data e valor inválidos. Peça que eu preveja qual falha ocorre e como preservar o número da linha no diagnóstico; depois proponha um pequeno ajuste sem mostrar a resposta antes.
A SEGUIR · AULA 06

Você precisa de uma lista, de um conjunto ou de uma busca por chave?

Aula escrita · Seu catálogo cresceu. Agora você precisa manter equipamentos, encontrar um deles pelo código e gerar uma lista dos disponíveis. Colocar tudo em uma ArrayList funciona para alguns requisitos, mas não responde automaticamente aos outros. Antes de decorar nomes, vamos escolher pelo comportamento necessário.

Ir para a aula 06 →