Java Essencial: Tipos primitivos, variáveis e conversões seguras

Capítulo 4

Tempo estimado de leitura: 8 minutos

+ Exercício

Tipos primitivos: visão geral e quando usar

Em Java, tipos primitivos são tipos de dados básicos, armazenados de forma eficiente e com comportamento previsível. Eles não são objetos e possuem tamanhos fixos. Use primitivos quando você precisa de desempenho, simplicidade e valores numéricos/lógicos diretos.

TipoTamanhoIntervalo (aprox.)Uso típico
booleanJVM-dependente (conceitual)true/falseFlags, validações, condições
byte8 bits-128 a 127Dados binários, buffers, I/O, protocolos
short16 bits-32.768 a 32.767Economia de memória em arrays grandes (raro)
int32 bits-2.147.483.648 a 2.147.483.647Inteiros em geral (padrão para contagens e índices)
long64 bits-9,22e18 a 9,22e18Timestamps, IDs grandes, contagens extensas
float32 bits (IEEE 754)~1,4e-45 a ~3,4e38 (com precisão limitada)Gráficos, sensores, cálculos aproximados
double64 bits (IEEE 754)~4,9e-324 a ~1,8e308 (mais precisão que float)Cálculos científicos/estatísticos aproximados
char16 bits0 a 65.535 (UTF-16 code unit)Caracteres, manipulação de texto em baixo nível

Observações importantes:

  • char é sem sinal (não existe char negativo).
  • float e double são aproximados: não use para dinheiro quando precisão decimal exata for necessária.
  • Para literais: long usa sufixo L (ex.: 10L), float usa F (ex.: 1.5F), double é o padrão para decimais (ex.: 1.5).

Literais numéricos e legibilidade

Você pode usar _ em literais para melhorar a leitura:

int populacao = 214_000_000;      // ok, mais legível
long distancia = 9_460_730_472_580L;
int binario = 0b1010_0101;        // binário
int hex = 0xFF_EC_DE_5E;          // hexadecimal

Declaração, inicialização e escopo de variáveis

Uma variável precisa de tipo, nome e, em muitos casos, inicialização. Variáveis locais (dentro de métodos) devem ser inicializadas antes do uso.

int idade = 30;
double altura = 1.75;
boolean ativo = true;
char inicial = 'J';

Boas práticas de nomes

  • Use nomes descritivos: totalPedido, quantidadeItens, taxaJuros.
  • Evite abreviações ambíguas: v, x1, tmp (a menos que o contexto seja óbvio).

Constantes com final

final indica que o valor não pode ser reatribuído após a inicialização. Em constantes, é comum usar caixa alta com underscore.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

final int DIAS_DA_SEMANA = 7;
final double PI = 3.141592653589793;
final long TIMEOUT_MS = 5_000L;

Em variáveis locais, final também ajuda a evitar reatribuições acidentais e melhora a clareza.

Inferência de tipo local com var (quando aplicável)

var permite que o compilador infira o tipo de uma variável local a partir do inicializador. Não é um tipo dinâmico: o tipo é fixado na compilação.

  • Disponível para variáveis locais (dentro de métodos, blocos, for).
  • Exige inicialização na mesma linha.
  • Não pode ser usado para campos de classe (em Java padrão) nem para parâmetros.
var quantidade = 10;          // int
var taxa = 0.15;              // double
var nome = "Ana";             // String
var ativo = true;             // boolean

Clareza: prefira var quando o tipo é óbvio pelo lado direito ou quando reduz repetição sem esconder informação importante.

// Bom: tipo evidente
var total = 0;

// Menos claro: o tipo inferido pode surpreender
var valor = 1;     // int, não long
var preco = 1.0;   // double, não BigDecimal

Conversões (casting): implícitas, explícitas e seguras

Widening (conversão implícita) — geralmente segura

Quando você atribui um tipo menor a um maior, o Java faz a conversão automaticamente (não há perda de magnitude, embora possa haver mudança de precisão ao ir para ponto flutuante).

int a = 100;
long b = a;        // int -> long (ok)

double c = b;      // long -> double (pode perder precisão em valores muito grandes)

Ordem típica de widening numérico: byteshortintlongfloatdouble. (char pode ir para int, long, etc.)

Narrowing (conversão explícita) — exige cuidado

Ao converter de um tipo maior para um menor, você precisa de cast explícito e pode ocorrer perda de dados.

long total = 3_000_000_000L;
int totalInt = (int) total;   // overflow: valor final não cabe em int

Passo a passo para um cast mais seguro:

  1. Verifique o intervalo do tipo de destino (Integer.MIN_VALUE e Integer.MAX_VALUE, por exemplo).
  2. Valide antes de converter.
  3. Se estiver fora do intervalo, trate (erro, ajuste, log, etc.).
long total = 3_000_000_000L;
if (total < Integer.MIN_VALUE || total > Integer.MAX_VALUE) {
    throw new IllegalArgumentException("Fora do intervalo de int: " + total);
}
int totalInt = (int) total;

Promoção numérica em expressões (pegadinhas comuns)

Em expressões aritméticas, tipos menores que int (byte, short, char) são promovidos para int. Isso afeta atribuições e pode exigir cast.

byte x = 10;
byte y = 20;
// byte soma = x + y; // erro: x+y é int
byte soma = (byte) (x + y);

Quando há mistura de tipos, o resultado tende ao tipo “mais largo” envolvido:

int i = 1;
long l = 2L;
var r1 = i + l;     // long

double d = 2.5;
var r2 = l + d;     // double

Overflow e underflow: como acontecem e como detectar

Overflow ocorre quando um valor ultrapassa o máximo do tipo; underflow quando fica abaixo do mínimo (ou, em ponto flutuante, quando fica tão pequeno que vira 0.0). Em inteiros, Java não lança erro automaticamente: o valor “dá a volta” (wrap-around).

int max = Integer.MAX_VALUE;
int estourou = max + 1;  // vira Integer.MIN_VALUE

int min = Integer.MIN_VALUE;
int estourou2 = min - 1; // vira Integer.MAX_VALUE

Operações seguras com Math.*Exact

Para inteiros, use métodos que lançam ArithmeticException em overflow:

int a = 2_000_000_000;
int b = 2_000_000_000;
int soma = Math.addExact(a, b); // lança ArithmeticException

Outros úteis: Math.subtractExact, Math.multiplyExact, Math.incrementExact, Math.decrementExact, Math.toIntExact(long).

Divisão, arredondamento e precisão

Divisão inteira vs. divisão com decimais

Se ambos os operandos são inteiros, a divisão é inteira (trunca a parte decimal).

int a = 5;
int b = 2;
int divInt = a / b;       // 2

double divDouble = a / (double) b; // 2.5

Arredondamento com Math

  • Math.round: arredonda para o inteiro mais próximo (retorna long para double e int para float).
  • Math.floor: arredonda para baixo (retorna double).
  • Math.ceil: arredonda para cima (retorna double).
double v = 10.6;
long r = Math.round(v);     // 11

double f = Math.floor(v);   // 10.0

double c = Math.ceil(v);    // 11.0

Por que double pode “errar” em decimais

Alguns valores decimais não têm representação binária exata. Isso pode aparecer em somas simples:

double x = 0.1 + 0.2;
System.out.println(x); // pode imprimir 0.30000000000000004

Para comparações, evite == com ponto flutuante; use tolerância (epsilon):

double esperado = 0.3;
double obtido = 0.1 + 0.2;
double eps = 1e-9;
boolean ok = Math.abs(esperado - obtido) < eps;

Validação de entrada numérica (passo a passo)

Ao ler números como texto (por exemplo, de um formulário, arquivo ou console), valide antes de converter para evitar exceções e garantir regras de negócio (faixa permitida, não-negativo, etc.).

Passo a passo: ler texto, validar e converter

  1. Receba a entrada como String.
  2. Tente converter com Integer.parseInt, Long.parseLong, Double.parseDouble, etc.
  3. Trate NumberFormatException.
  4. Valide regras (intervalo, mínimo/máximo, etc.).
String entrada = "42";
int valor;
try {
    valor = Integer.parseInt(entrada);
} catch (NumberFormatException e) {
    throw new IllegalArgumentException("Número inválido: " + entrada);
}

if (valor < 0 || valor > 120) {
    throw new IllegalArgumentException("Idade fora do intervalo: " + valor);
}

Para decimais, cuidado com separador: Double.parseDouble espera ponto (.). Se sua entrada vier com vírgula, normalize:

String entrada = "10,5";
entrada = entrada.replace(',', '.');
double valor = Double.parseDouble(entrada);

Exercícios práticos (com foco em cálculos, arredondamento e validação)

1) Conversão segura de long para int

Objetivo: ler um número (como String), converter para long e depois para int apenas se couber.

  • Entrada: "3000000000" (3 bilhões)
  • Saída esperada: erro informando que está fora do intervalo de int
String entrada = "3000000000";
long valor = Long.parseLong(entrada);

int convertido;
try {
    convertido = Math.toIntExact(valor);
} catch (ArithmeticException e) {
    throw new IllegalArgumentException("Fora do intervalo de int: " + valor);
}

2) Cálculo de média com divisão correta

Objetivo: calcular a média de 3 inteiros e retornar um double com uma casa decimal.

  • Valores: 7, 8, 10
  • Média: 8.3
int n1 = 7, n2 = 8, n3 = 10;
double media = (n1 + n2 + n3) / 3.0;
double media1casa = Math.round(media * 10.0) / 10.0; // 1 casa
System.out.println(media1casa);

3) Simulador de desconto com arredondamento

Objetivo: aplicar 12% de desconto e arredondar para 2 casas decimais (aproximação com double).

  • Preço: 199.90
  • Desconto: 12%
  • Preço final (2 casas): 175.91
double preco = 199.90;
double desconto = 0.12;

double finalBruto = preco * (1.0 - desconto);
double final2casas = Math.round(finalBruto * 100.0) / 100.0;
System.out.println(final2casas);

4) Detectando overflow em multiplicação

Objetivo: multiplicar dois int com segurança e tratar overflow.

  • Valores: 50.000 e 50.000
  • Resultado real: 2.500.000.000 (não cabe em int)
int a = 50_000;
int b = 50_000;

try {
    int prod = Math.multiplyExact(a, b);
    System.out.println(prod);
} catch (ArithmeticException e) {
    System.out.println("Overflow ao multiplicar: " + a + " * " + b);
}

5) Validação de entrada numérica com faixa e fallback

Objetivo: receber uma String que deveria ser um percentual inteiro de 0 a 100. Se inválido, usar 0.

String entrada = "105"; // teste com "abc", "-1", "50"
int percentual;
try {
    percentual = Integer.parseInt(entrada);
    if (percentual < 0 || percentual > 100) {
        percentual = 0;
    }
} catch (NumberFormatException e) {
    percentual = 0;
}
System.out.println(percentual);

6) Promoção numérica e cast consciente

Objetivo: somar dois byte e armazenar em byte sem perder o controle do que acontece.

  • Teste com valores pequenos (10 + 20) e depois com valores que estouram (120 + 120).
byte x = 120;
byte y = 120;
int somaInt = x + y;              // promoção para int
System.out.println(somaInt);      // 240

byte somaByte = (byte) (x + y);   // overflow em byte (-16)
System.out.println(somaByte);

Tarefa: implemente uma validação para impedir a conversão para byte quando somaInt estiver fora de Byte.MIN_VALUE e Byte.MAX_VALUE.

Agora responda o exercício sobre o conteúdo:

Ao tentar converter um valor long para int, qual abordagem é mais segura para evitar que um número fora do intervalo de int seja convertido silenciosamente com overflow?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Converter de long para int é narrowing e pode causar overflow. O mais seguro é validar o intervalo (ou usar Math.toIntExact, que lança exceção) e tratar valores fora do limite antes do cast.

Próximo capitúlo

Java Essencial: Tipos por referência, null e noções de memória

Arrow Right Icon
Capa do Ebook gratuito Java Essencial: Fundamentos da Linguagem e do Ecossistema (JDK, IDE, Maven)
22%

Java Essencial: Fundamentos da Linguagem e do Ecossistema (JDK, IDE, Maven)

Novo curso

18 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.