O tipo limita valores, o contrato dá-lhes significado
Um serviço recebe retries como texto, converte-o para int e chama uma operação. São três decisões distintas. O texto tem de ser convertível, o valor tem de caber no tipo e a regra do serviço pode exigir apenas 0 a 5 tentativas. O inteiro -2 satisfaz a representação int e continua a violar esse contrato. Não transformes automaticamente uma entrada inválida em ausência: uma chave não fornecida pode permitir um valor por omissão, enquanto uma chave explicitamente inválida pode impedir o arranque.
Os tipos primitivos guardam valores, como int, long, double e boolean. Uma variável Integer é uma referência para um wrapper e pode ser null. Usa essa distinção ao ler a assinatura de uma API: int não representa ausência através de null; Integer permite-a, mas o código consumidor tem de decidir o que ela significa. Uma declaração de tipo não substitui regras de intervalo, unidade ou obrigatoriedade.
Inicialização e unboxing
Os campos e os elementos de arrays recebem valores iniciais por omissão: zero para int, false para boolean e null para referências. Uma variável local declarada dentro de um método exige atribuição antes da leitura. Assim, int retries; seguido por use(retries); falha na compilação. Não depende do conteúdo antigo da memória.
Com Integer limit = null, a declaração é válida. A expressão limit > 0 precisa de obter o int do wrapper, operação chamada unboxing, e lança NullPointerException. O problema surge na utilização numérica. Se null deve ser rejeitado, a condição limit != null && limit > 0 protege essa utilização: && só avalia a direita quando a esquerda é verdadeira. O operador booleano & avalia ambos os lados; colocar a comparação antes da guarda também perde a proteção. Um valor por omissão só deve ser aplicado se o contrato o autorizar.
O tipo da operação vem antes do destino
Considera int accepted = 7; int total = 10; double ratio = accepted / total; os operandos de / são inteiros: o quociente é 0 e só depois passa a 0.0. Para uma proporção fracionária, ((double) accepted) / total alarga um operando antes da divisão. Também é necessário definir o caso total igual a zero; mudar o tipo não define uma política para ausência de população.
A mesma ordem importa em produtos. Dois int com valor 50 000 têm produto matemático 2 500 000 000, que não cabe em int. long size = rows * width continua a calcular o produto int antes da atribuição. (long) rows * width calcula-o como long; (long) (rows * width) converte tarde demais. Se a saída tem de continuar a ser int, Math.multiplyExact(a, b), com argumentos int, permite sinalizar overflow por ArithmeticException. Um cast sem verificação pode perder informação; aceitar apenas produtos positivos também não prova ausência de overflow.
Precisão e valores de fronteira
Um identificador long não é uma medida aproximada. Passá-lo por double pode perder precisão mesmo sem exceder a gama de magnitude de double. Um cast posterior não recupera os bits perdidos. Numa integração que exige identidade exata, conserva o inteiro ou uma representação textual definida pelo contrato.
A divisão inteira trunca em direção a zero: -7 / 2 é -3. Dividir um inteiro por zero lança ArithmeticException. Há uma fronteira diferente: Integer.MIN_VALUE / -1 tem quociente matemático fora de int e devolve MIN_VALUE sem lançar. Uma promessa de deteção de overflow exige tratar esse caso explicitamente.
Em medições double, NaN não se comporta como um número ordenado. Uma regra que rejeita x < 0.0 || x > 100.0 e aceita o resto admite NaN. A regra positiva x >= 0.0 && x <= 100.0 rejeita-o, ou pode usar-se Double.isNaN(x) na rejeição. x == Double.NaN não o deteta. A unidade e os limites continuam a ser decisões do domínio.
Aplicação guiada: configuração de arranque
Um serviço fictício lê timeoutSeconds. A chave ausente significa 30 segundos; um valor presente tem de ser inteiro entre 1 e 120. Compara quatro entradas: ausência, "45", "later" e "-1". Primeiro decide se existe uma entrada. Só a ausência recebe 30. Para as restantes, Integer.parseInt permite obter um int ou sinalizar texto não convertível. A conversão de "45" permite continuar; "later" falha na conversão; "-1" converte mas falha no intervalo.
Não uses um único catch que transforme todos os erros em zero. Isso apagaria a diferença entre um lapso no ficheiro e a escolha deliberada de um timeout. O diagnóstico deve identificar a chave e a classe de erro, sem copiar segredos de outras configurações. Antes de aceitar a alteração, compara também os limites 1 e 120 e os vizinhos 0 e 121. A avaliação pede que escolhas a política e interpretes os resultados descritos, sem executar código.
Decisão: validar antes de reservar capacidade
Um importador recebe rows e width, ambos int não negativos e limitados a 100 000. O contrato permite no máximo 1 000 000 de posições. O produto máximo das entradas cabe em long. Calcula o produto alargando um operando; compara-o com o limite do serviço; só depois converte para int e reserva. Mesmo um tipo capaz de representar o produto não autoriza uma reserva que viole o limite.
Ao rever uma proposta, segue a sequência valor de entrada → tipo dos operandos → resultado calculado → validação → conversão final → efeito. Mudar apenas a variável de destino deixa um erro anterior intacto. Reservar primeiro e verificar depois também usa uma informação que já pode ter sido corrompida. Este raciocínio aplica-se a tamanhos de lotes, limites de paginação e estimativas de capacidade, sempre com as unidades e os limites do serviço explícitos.
static int timeoutSeconds(String raw) {
if (raw == null) return 30;
int seconds = Integer.parseInt(raw);
if (seconds < 1 || seconds > 120) {
throw new IllegalArgumentException("timeoutSeconds outside range");
}
return seconds;
}O excerto é um método a ler dentro de uma classe. Assume que null representa chave ausente. "45" devolve 45; "later" origina NumberFormatException; "-1" chega à rejeição de intervalo. O diagnóstico de arranque pode distinguir os dois tipos de falha.
Armadilhas comuns
Confundir wrapper com primitivo; aplicar zero por omissão a uma variável local; converter depois da operação; tratar parseInt como validação do domínio; usar == para detetar NaN.
Tópicos relacionados: Referências, identidade e texto · Métodos, contratos e fluxo de controlo · Decimais, tempo e fronteiras
Primeiro define presença, unidades e limites. Depois confirma o tipo de cada operação e valida o resultado antes de produzir efeitos.
Referência: JLS 25: Types, Values, and Variables · Java SE 25; no preview features