Tipos Primitivos, Wrapper Classes e Autoboxing
Java tem oito tipos primitivos armazenados diretamente na pilha (stack). Cada primitivo possui uma classe wrapper correspondente no pacote java.lang, usada quando o contexto exige um objeto (coleções, generics). O compilador faz a conversão automática (autoboxing/unboxing), mas isso esconde custos e armadilhas.
// Tipos primitivos e seus ranges
byte b = 127; // -128 a 127
short s = 32_767; // separador de milhar: underline
int i = 2_000_000;
long l = 9_000_000_000L; // sufixo L obrigatório
float f = 3.14f; // sufixo f obrigatório
double d = 3.14159265; // padrão para ponto flutuante
boolean flag = true;
char c = 'A'; // UTF-16, valor 65
// Wrapper classes — necessárias em coleções
Integer wrapped = Integer.valueOf(42); // preferível a new Integer(42)
int unwrapped = wrapped; // unboxing automático
// Autoboxing em coleções
List<Integer> numeros = new ArrayList<>();
numeros.add(10); // autoboxing: int → Integer
int primeiro = numeros.get(0); // unboxing: Integer → int
// ARMADILHA: cache de Integer entre -128 e 127
Integer a1 = 127;
Integer a2 = 127;
System.out.println(a1 == a2); // true — mesmo objeto em cache!
Integer b1 = 200;
Integer b2 = 200;
System.out.println(b1 == b2); // false — objetos distintos
System.out.println(b1.equals(b2)); // true — compare sempre com equals()
// ARMADILHA: NullPointerException silencioso no unboxing
Integer valor = null;
int x = valor; // lança NullPointerException — unboxing de null!
// Conversões seguras
int parsed = Integer.parseInt("42");
double parseD = Double.parseDouble("3.14");
String fromInt = Integer.toString(42);
String fromInt2 = String.valueOf(42); // alternativa mais idiomáticaString, StringBuilder e Métodos de String
String é imutável em Java — cada operação que “modifica” uma string cria um novo objeto. Para concatenações em laço, use StringBuilder. A partir do Java 11, novos métodos utilitários foram adicionados diretamente na classe String.
String nome = "Rafael Marques";
String vazia = "";
String branco = " ";
// Métodos essenciais
nome.length(); // 14
nome.toUpperCase(); // "RAFAEL MARQUES"
nome.toLowerCase(); // "rafael marques"
nome.trim(); // remove espaços nas bordas (ignora Unicode)
branco.strip(); // Java 11+ — Unicode-aware, preferível ao trim()
branco.stripLeading(); // só à esquerda
branco.stripTrailing(); // só à direita
vazia.isEmpty(); // true — length == 0
branco.isBlank(); // Java 11+ — true se só whitespace
"oi\noi\noi".lines().count(); // Java 11+ → 3L
"ha".repeat(3); // Java 11+ → "hahaha"
// Busca e substituição
nome.contains("Rafael"); // true
nome.startsWith("Rafael"); // true
nome.endsWith("Marques"); // true
nome.indexOf("a"); // 1 (primeira ocorrência)
nome.lastIndexOf("a"); // 12
nome.replace("Rafael", "João"); // "João Marques"
nome.replaceAll("[aeiou]", "*"); // regex — "R*f**l M*rqu*s"
// Divisão — ARMADILHA: argumento é regex!
"a.b.c".split("\\."); // correto: ["a","b","c"]
"a.b.c".split("."); // ERRADO: split em cada char → []
// Formatação
String msg = String.format("Pedido #%d — R$ %.2f", 1001, 299.90);
String msg2 = "Pedido #%d — R$ %.2f".formatted(1001, 299.90); // Java 15+
// StringBuilder — eficiente para muitas concatenações
StringBuilder sb = new StringBuilder();
for (String item : List.of("pão", "leite", "ovos")) {
sb.append(item).append(", ");
}
sb.deleteCharAt(sb.length() - 1); // remove última vírgula
sb.deleteCharAt(sb.length() - 1); // remove último espaço
String lista = sb.toString(); // "pão, leite, ovos"
// String.join e Collectors.joining
String unido = String.join(" | ", "A", "B", "C"); // "A | B | C"
// ARMADILHA: == compara referência, não conteúdo!
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true — sempre use equals()Arrays e Varargs
Arrays em Java têm tamanho fixo definido na criação. Para listas dinâmicas, prefira ArrayList. Varargs (...) permite passar zero ou mais argumentos do mesmo tipo, recebidos internamente como array.
import java.util.Arrays;
// Declaração e inicialização
int[] notas = new int[5]; // todos zeros
String[] produtos = {"Notebook", "Mouse", "Teclado"};
int[][] matriz = new int[3][3]; // array bidimensional
// Operações básicas
int len = produtos.length; // 3 — campo, não método!
Arrays.sort(produtos); // ordena in-place
int idx = Arrays.binarySearch(produtos, "Mouse"); // busca binária (array deve estar ordenado)
int[] copia = Arrays.copyOf(notas, notas.length); // cópia com mesmo tamanho
int[] range = Arrays.copyOfRange(notas, 1, 4); // cópia parcial [1, 4)
Arrays.fill(notas, 10); // preenche com valor
String repr = Arrays.toString(produtos); // "[Mouse, Notebook, Teclado]"
// Converter array → List (atenção: tamanho fixo!)
List<String> listaProd = Arrays.asList(produtos); // não suporta add/remove
List<String> listaFlex = new ArrayList<>(Arrays.asList(produtos)); // mutável
// Varargs — zero ou mais argumentos; internamente é um array
public static BigDecimal somarTotais(BigDecimal... valores) {
BigDecimal total = BigDecimal.ZERO;
for (BigDecimal v : valores) { // enhanced for funciona normalmente
total = total.add(v);
}
return total;
}
// Chamando varargs
somarTotais(); // array vazio
somarTotais(new BigDecimal("10.00")); // um elemento
somarTotais(new BigDecimal("10"), new BigDecimal("20"), new BigDecimal("30")); // três
// ARMADILHA: varargs com generics gera unchecked warning
// Use @SafeVarargs em métodos final, static ou privados que não escrevem no array
@SafeVarargs
public static <T> List<T> listaDe(T... elementos) {
return List.of(elementos);
}Control Flow: if/else, switch, for, while, labels
Java oferece as estruturas clássicas de fluxo mais o switch expression (Java 14+) que elimina o break e permite retornar valores. Labels permitem controle em loops aninhados.
// if/else convencional
int estoque = 3;
if (estoque > 10) {
System.out.println("Estoque alto");
} else if (estoque > 0) {
System.out.println("Estoque baixo — repor em breve");
} else {
System.out.println("Sem estoque");
}
// Switch expression (Java 14+) — retorna valor, sem fall-through, sem break
String descricao = switch (estoque) {
case 0 -> "Esgotado";
case 1, 2 -> "Crítico";
default -> estoque > 10 ? "Abundante" : "Normal";
};
// Switch expression com bloco yield
double desconto = switch (cliente.categoria()) {
case "VIP" -> 0.20;
case "PREMIUM" -> 0.10;
default -> {
// bloco com lógica extra — usa yield para retornar
double base = 0.05;
double ajuste = cliente.pontos() > 1000 ? 0.02 : 0.0;
yield base + ajuste;
}
};
// for clássico e enhanced for
int[] quantidades = {5, 3, 8, 1, 2};
int soma = 0;
for (int n : quantidades) { // enhanced for — mais limpo quando não precisa do índice
soma += n;
}
for (int j = 0; j < quantidades.length; j++) { // clássico — quando precisa do índice
System.out.println(j + ": " + quantidades[j]);
}
// while e do-while
int tentativas = 0;
while (tentativas < 3 && !pedido.processado()) {
pedido.tentar();
tentativas++;
}
// Labels — break/continue em loop externo
externo:
for (String categoria : categorias) {
for (Produto p : categoria.produtos()) {
if (p.estoque() == 0) {
System.out.println("Ruptura em: " + categoria.nome());
continue externo; // pula para próxima categoria
}
processar(p);
}
}Classes, Construtores, Modificadores de Acesso, this, static
Uma classe define o blueprint de um objeto. Modificadores de acesso controlam visibilidade. this refere-se à instância atual. Membros static pertencem à classe, não às instâncias.
public class Pedido {
// Campos — prefira private com getters/setters
private final String id; // final: atribuído apenas no construtor
private BigDecimal total;
private Status status;
private static int contadorGlobal = 0; // compartilhado entre todas as instâncias
public static final String MOEDA = "BRL"; // constante pública — SCREAMING_SNAKE_CASE
// Construtor principal
public Pedido(String id, BigDecimal total) {
this.id = id; // this.id: campo; id: parâmetro
this.total = total;
this.status = Status.RASCUNHO;
contadorGlobal++;
}
// Construtor alternativo — delega ao principal via this()
public Pedido(BigDecimal total) {
this("PED-" + System.currentTimeMillis(), total);
}
// Método de instância
public void confirmar() {
if (status != Status.RASCUNHO)
throw new IllegalStateException("Pedido já confirmado");
this.status = Status.CONFIRMADO;
}
// Método static — não acessa this, não acessa campos de instância
public static int totalPedidos() {
return contadorGlobal;
}
// Getters (sem setters para campos final)
public String getId() { return id; }
public BigDecimal getTotal() { return total; }
public Status getStatus() { return status; }
}
// Uso
Pedido p = new Pedido(new BigDecimal("250.00"));
p.confirmar();
System.out.println(Pedido.totalPedidos()); // acesso via nome da classe, não instância
System.out.println(Pedido.MOEDA); // "BRL"Interfaces e Classes Abstratas
Interfaces definem contratos (o quê), classes abstratas definem comportamento parcial (como). Use interface quando múltiplos tipos não relacionados compartilham o mesmo comportamento. Use classe abstrata quando existe estado compartilhado ou implementação base comum.
// Interface — pode ter default methods (Java 8+) e static methods
public interface Pagavel {
BigDecimal calcularTotal(); // contrato obrigatório
default boolean isGratis() { // implementação padrão — pode sobrescrever
return calcularTotal().compareTo(BigDecimal.ZERO) == 0;
}
static Pagavel desconto(Pagavel p, double pct) { // método utilitário na interface
return () -> p.calcularTotal().multiply(BigDecimal.valueOf(1 - pct));
}
}
// Classe abstrata — pode ter construtores, campos, métodos concretos
public abstract class DocumentoFiscal {
private final String numero;
private final LocalDate emissao;
protected DocumentoFiscal(String numero) { // protected: acessível em subclasses
this.numero = numero;
this.emissao = LocalDate.now();
}
public abstract BigDecimal calcularImposto(); // subclasse deve implementar
// Template method — define o algoritmo, delega os passos
public final void emitir() { // final: não pode sobrescrever
validar();
BigDecimal imposto = calcularImposto();
persistir(numero, emissao, imposto);
notificarContabilidade();
}
protected void validar() { /* validação padrão */ }
private void persistir(String n, LocalDate d, BigDecimal t) { /* ... */ }
private void notificarContabilidade() { /* ... */ }
public String getNumero() { return numero; }
}
// Herança combinada: estende abstrata + implementa interface
public class NotaFiscal extends DocumentoFiscal implements Pagavel {
private final BigDecimal valor;
public NotaFiscal(String numero, BigDecimal valor) {
super(numero);
this.valor = valor;
}
@Override public BigDecimal calcularImposto() { return valor.multiply(new BigDecimal("0.12")); }
@Override public BigDecimal calcularTotal() { return valor.add(calcularImposto()); }
}Herança, Override, super, Polimorfismo
Herança (extends) permite que uma subclasse herde campos e métodos da superclasse. @Override é obrigatório por boa prática — o compilador verifica que realmente existe método a sobrescrever. super acessa membros da superclasse.
public class Produto {
private final String sku;
private final BigDecimal precoBase;
public Produto(String sku, BigDecimal precoBase) {
this.sku = sku;
this.precoBase = precoBase;
}
public BigDecimal calcularPreco() {
return precoBase;
}
public String descricao() {
return "Produto[sku=" + sku + ", preco=" + precoBase + "]";
}
}
public class ProdutoPromocional extends Produto {
private final double desconto; // 0.0 a 1.0
public ProdutoPromocional(String sku, BigDecimal precoBase, double desconto) {
super(sku, precoBase); // chama construtor da superclasse — deve ser primeira instrução
this.desconto = desconto;
}
@Override
public BigDecimal calcularPreco() {
BigDecimal fator = BigDecimal.valueOf(1 - desconto);
return super.calcularPreco().multiply(fator); // reutiliza lógica da superclasse
}
@Override
public String descricao() {
return super.descricao() + " [desconto=" + (desconto * 100) + "%]";
}
}
// Polimorfismo — variável do tipo base, comportamento do tipo real
List<Produto> catalogo = List.of(
new Produto("SKU-001", new BigDecimal("100.00")),
new ProdutoPromocional("SKU-002", new BigDecimal("100.00"), 0.15)
);
for (Produto p : catalogo) {
System.out.println(p.descricao() + " → " + p.calcularPreco()); // chama método correto
}
// Produto[sku=SKU-001, preco=100.00] → 100.00
// Produto[sku=SKU-002, preco=100.00] [desconto=15.0%] → 85.00Generics: Bounded Wildcards e Princípio PECS
Generics permitem escrever código que funciona com qualquer tipo mantendo segurança de tipos em tempo de compilação. Wildcards com ? extends T e ? super T controlam a variância. O princípio PECS (Producer Extends, Consumer Super) guia qual usar.
// Generic method — funciona com qualquer tipo comparável
public static <T extends Comparable<T>> T maximo(List<T> lista) {
return lista.stream().max(Comparator.naturalOrder())
.orElseThrow(() -> new NoSuchElementException("Lista vazia"));
}
// PECS: Producer Extends
// — lemos de uma fonte (produce) de Numeros: usa ? extends
public static BigDecimal somarTotais(List<? extends Number> numeros) {
BigDecimal soma = BigDecimal.ZERO;
for (Number n : numeros) {
soma = soma.add(new BigDecimal(n.toString()));
}
return soma;
}
somarTotais(List.of(1, 2, 3)); // Integer extends Number ✓
somarTotais(List.of(1.5, 2.5)); // Double extends Number ✓
// PECS: Consumer Super
// — escrevemos em um destino (consume): usa ? super
public static void copiarPedidos(List<Pedido> origem, List<? super Pedido> destino) {
destino.addAll(origem);
}
List<Object> objetos = new ArrayList<>();
copiarPedidos(pedidos, objetos); // Object super Pedido ✓
// Classe genérica com múltiplos bounds
public class Repositorio<T extends Entidade & Auditavel> {
private final Map<String, T> store = new HashMap<>();
public void salvar(T entidade) {
entidade.setUltimaAtualizacao(Instant.now()); // método de Auditavel
store.put(entidade.getId(), entidade); // método de Entidade
}
public Optional<T> buscar(String id) {
return Optional.ofNullable(store.get(id));
}
}
// ARMADILHA: type erasure — generics não existem em runtime
List<String> strings = new ArrayList<>();
List<Integer> inteiros = new ArrayList<>();
strings.getClass() == inteiros.getClass(); // true! ambos são ArrayList em runtimeRecords (Java 16)
Record é uma classe imutável de dados com construtor canônico, acessores, equals, hashCode e toString gerados automaticamente pelo compilador. Ideal para DTOs, Value Objects e respostas de API.
// Record básico — todos os campos são final e acessíveis sem "get" prefixo
public record ProdutoDTO(String sku, String nome, BigDecimal preco, int estoque) {}
// Record com construtor compacto — validação sem repetir parâmetros
public record Money(BigDecimal amount, String currency) {
public Money { // construtor compacto — sem parênteses com parâmetros explícitos
Objects.requireNonNull(amount, "amount não pode ser null");
Objects.requireNonNull(currency, "currency não pode ser null");
if (amount.compareTo(BigDecimal.ZERO) < 0)
throw new IllegalArgumentException("Valor monetário não pode ser negativo");
amount = amount.setScale(2, RoundingMode.HALF_UP); // pode reassignar no compacto
}
// Métodos de negócio são permitidos
public Money somar(Money outra) {
if (!currency.equals(outra.currency))
throw new IllegalArgumentException("Moedas diferentes: " + currency + " vs " + outra.currency);
return new Money(amount.add(outra.amount), currency);
}
public Money aplicarDesconto(double percentual) {
BigDecimal fator = BigDecimal.valueOf(1 - percentual);
return new Money(amount.multiply(fator), currency);
}
public boolean isZero() { return amount.compareTo(BigDecimal.ZERO) == 0; }
}
// Records em pattern matching (Java 21) — deconstruct direto no switch
public record Coordenada(double lat, double lon) {}
String regiao = switch (coord) {
case Coordenada(double lat, double lon) when lat > 0 && lon > 0 -> "Nordeste";
case Coordenada(double lat, double lon) when lat > 0 -> "Noroeste";
case Coordenada(double lat, double lon) when lon > 0 -> "Sudeste";
default -> "Sudoeste";
};
// Uso
var preco = new Money(new BigDecimal("99.90"), "BRL");
preco.amount(); // BigDecimal — acessor gerado
preco.currency(); // String — acessor geradoSealed Classes e Interfaces (Java 17)
Sealed classes e interfaces restringem explicitamente quais tipos podem estendê-las. Combinadas com pattern matching, permitem verificação exaustiva em tempo de compilação — o compilador sabe todos os casos possíveis.
// Hierarquia fechada de eventos de pagamento
public sealed interface EventoPagamento
permits PagamentoAprovado, PagamentoRecusado, PagamentoPendente, PagamentoEstornado {}
public record PagamentoAprovado(String id, BigDecimal valor, Instant momento) implements EventoPagamento {}
public record PagamentoRecusado(String id, String motivo, int codigoErro) implements EventoPagamento {}
public record PagamentoPendente(String id, BigDecimal valor, String gateway) implements EventoPagamento {}
public record PagamentoEstornado(String id, BigDecimal valorDevolvido) implements EventoPagamento {}
// Processamento exaustivo — switch sem default porque o compilador garante cobertura total
public String processarEvento(EventoPagamento evento) {
return switch (evento) {
case PagamentoAprovado a -> "Aprovado R$ " + a.valor() + " em " + a.momento();
case PagamentoRecusado r -> "Recusado: " + r.motivo() + " (cod " + r.codigoErro() + ")";
case PagamentoPendente p -> "Aguardando confirmação no gateway " + p.gateway();
case PagamentoEstornado e -> "Estornado R$ " + e.valorDevolvido();
// sem default: se adicionar novo subtipo, o compilador exige novo case
};
}
// Sealed class com herança não-record (permite estado mutável)
public sealed abstract class Transacao permits Debito, Credito, Transferencia {
protected final String id;
protected final BigDecimal valor;
protected TransacaoStatus status;
protected Transacao(String id, BigDecimal valor) {
this.id = id;
this.valor = valor;
this.status = TransacaoStatus.PENDENTE;
}
public abstract void executar();
}
public non-sealed class Debito extends Transacao { // non-sealed: abre a hierarquia aqui
public Debito(String id, BigDecimal valor) { super(id, valor); }
@Override public void executar() {
// qualquer classe pode estender Debito
this.status = TransacaoStatus.CONCLUIDA;
}
}Pattern Matching: instanceof, switch e Deconstruction (Java 16–21)
Pattern matching elimina casts manuais e verbosidade. instanceof com binding (Java 16), switch com patterns (Java 21) e record deconstruction patterns (Java 21) formam um sistema coeso de verificação de tipo e extração de dados.
// instanceof com binding — Java 16+
Object obj = obterResposta();
if (obj instanceof String s && s.length() > 0) {
System.out.println("Texto não vazio: " + s.toUpperCase()); // s é String aqui
}
// Guarded patterns — when adiciona condição ao case
public BigDecimal calcularFrete(Object destinatario) {
return switch (destinatario) {
case ClienteVIP vip when vip.gastoAnual().compareTo(new BigDecimal("10000")) > 0
-> BigDecimal.ZERO; // frete grátis
case ClienteVIP vip -> new BigDecimal("10.00"); // VIP mas abaixo do limite
case ClientePF pf -> new BigDecimal("15.00");
case ClientePJ pj -> new BigDecimal("25.00");
case null -> throw new NullPointerException("Destinatário nulo");
default -> new BigDecimal("20.00");
};
}
// Record deconstruction — extrai campos diretamente no case (Java 21)
public record Endereco(String rua, String cidade, String estado) {}
public record Pedido(String id, Endereco entrega, BigDecimal total) {}
String label = switch (pedido) {
case Pedido(var id, Endereco(var r, var c, "SP"), var total)
when total.compareTo(new BigDecimal("500")) > 0
-> "Entrega expressa em " + c + ": " + r;
case Pedido(var id, Endereco(var r, var c, var uf), var total)
-> "Entrega padrão em " + c + "/" + uf;
};
// ARMADILHA: null não faz match em nenhum pattern — trate explicitamente
Object valor = null;
String resultado = switch (valor) {
case null -> "nulo"; // Java 21 aceita case null
case Integer i -> "inteiro";
default -> "outro";
};Text Blocks (Java 15)
Text blocks simplificam strings multilinhas eliminando concatenações e escape de aspas. O recuo base é determinado pela posição das aspas de fechamento """ — tudo à esquerda delas é descartado.
// JSON sem escapar aspas duplas
String body = """
{
"pedido": {
"id": "PED-1001",
"status": "CONFIRMADO",
"total": 299.90
}
}
""";
// As aspas """ na coluna 8 definem o recuo — 8 espaços removidos de cada linha
// SQL legível
String sql = """
SELECT p.id, p.numero, c.nome AS cliente, p.total
FROM pedidos p
JOIN clientes c ON c.id = p.cliente_id
WHERE p.status = 'CONFIRMADO'
AND p.data_criacao >= :inicio
ORDER BY p.data_criacao DESC
""";
// HTML para templates de e-mail
String html = """
<html>
<body>
<h1>Pedido confirmado!</h1>
<p>Total: R$ %.2f</p>
</body>
</html>
""".formatted(total); // interpolação via formatted()
// Aspas de fechamento na mesma linha — sem newline final
String semNewline = """
linha única""";
// Sequências de escape especiais em text blocks
String semRecuo = """
linha 1 \
linha 2"""; // \ no final suprime a quebra de linha → "linha 1 linha 2"
String retainSpace = "fim \s"; // \s mantém espaços antes do trim automáticoEnums — Campos, Métodos e Abstract Methods
Enums em Java são classes completas — cada constante é uma instância singleton da classe. Podem ter campos, construtores, métodos e até métodos abstratos que cada constante implementa de forma diferente.
public enum StatusPedido {
RASCUNHO("Rascunho", false),
CONFIRMADO("Confirmado", true),
EM_SEPARACAO("Em Separação", true),
ENVIADO("Enviado", true),
ENTREGUE("Entregue", false),
CANCELADO("Cancelado", false);
private final String label;
private final boolean ativo;
// Construtor — sempre private (implícito se omitido)
StatusPedido(String label, boolean ativo) {
this.label = label;
this.ativo = ativo;
}
public String getLabel() { return label; }
public boolean isAtivo() { return ativo; }
// Método de negócio
public boolean podeSerCancelado() {
return this == RASCUNHO || this == CONFIRMADO;
}
}
// Enum com método abstrato — cada constante tem comportamento próprio
public enum OperacaoFinanceira {
CREDITO {
@Override
public BigDecimal aplicar(BigDecimal saldo, BigDecimal valor) {
return saldo.add(valor);
}
},
DEBITO {
@Override
public BigDecimal aplicar(BigDecimal saldo, BigDecimal valor) {
if (saldo.compareTo(valor) < 0)
throw new IllegalStateException("Saldo insuficiente");
return saldo.subtract(valor);
}
};
public abstract BigDecimal aplicar(BigDecimal saldo, BigDecimal valor);
}
// Operações úteis em enums
StatusPedido s = StatusPedido.CONFIRMADO;
s.name(); // "CONFIRMADO" — nome da constante
s.ordinal(); // 1 — posição (frágil: não use para persistência)
StatusPedido.valueOf("ENVIADO"); // busca por nome
StatusPedido.values(); // array com todos os valores
// EnumSet e EnumMap — mais eficientes que Set/Map genéricos
EnumSet<StatusPedido> ativos = EnumSet.of(CONFIRMADO, EM_SEPARACAO, ENVIADO);
EnumMap<StatusPedido, Integer> contagem = new EnumMap<>(StatusPedido.class);Annotations — Built-in e Custom com @Retention e @Target
Annotations são metadados que podem ser processados em tempo de compilação, pré-deploy ou em runtime via reflection. Annotations built-in do Java mais annotations customizadas com @Retention e @Target são amplamente usadas em frameworks como Spring e Hibernate.
// Annotations built-in mais importantes
@Override // garante que sobrescreve método da superclasse
@Deprecated // marca método/classe como obsoleto; gera warning
@SuppressWarnings("unchecked") // suprime avisos específicos do compilador
@FunctionalInterface // garante que a interface tem exatamente 1 método abstrato
@SafeVarargs // suprime unchecked warning em varargs genéricos
// Criando annotation customizada
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) // disponível em runtime via reflection
@Target({ElementType.METHOD, ElementType.TYPE}) // onde pode ser aplicada
public @interface Auditavel {
String valor() default "default"; // elemento com valor padrão
String[] operacoes() default {}; // array de strings
}
// Usando a annotation
@Auditavel(valor = "pedidos", operacoes = {"criar", "confirmar"})
public class PedidoService {
@Auditavel(valor = "cancelamento")
public void cancelar(String pedidoId) { /* ... */ }
}
// Lendo annotations em runtime via reflection
Class<?> clazz = PedidoService.class;
Auditavel ann = clazz.getAnnotation(Auditavel.class);
if (ann != null) {
System.out.println("Auditar: " + ann.valor());
System.out.println("Operações: " + Arrays.toString(ann.operacoes()));
}
// Iterando métodos anotados
for (Method m : clazz.getDeclaredMethods()) {
if (m.isAnnotationPresent(Auditavel.class)) {
Auditavel ma = m.getAnnotation(Auditavel.class);
System.out.println("Método auditado: " + m.getName() + " → " + ma.valor());
}
}Collections: List, Set, Map, Deque e Utilitários
O Java Collections Framework oferece estruturas de dados ricas. Escolha a implementação correta para cada caso de uso. Imutáveis via List.of, Set.of, Map.of (Java 9+) são thread-safe e previnem modificações acidentais.
// LIST — ordered, allows duplicates
List<String> mutavel = new ArrayList<>(List.of("A", "B", "C")); // mutável
List<String> fixo = List.of("A", "B", "C"); // imutável — lança UnsupportedOperationException
mutavel.add("D");
mutavel.add(0, "Z"); // insere na posição
mutavel.remove("B"); // remove por valor
mutavel.remove(1); // remove por índice
mutavel.set(0, "X"); // substitui
Collections.sort(mutavel);
Collections.shuffle(mutavel);
Collections.reverse(mutavel);
// LinkedList — eficiente para inserção/remoção nas extremidades
Deque<String> fila = new LinkedList<>(List.of("primeiro", "segundo"));
fila.addFirst("novo-primeiro");
fila.addLast("novo-ultimo");
String cabeca = fila.peekFirst(); // lê sem remover
String removido = fila.pollFirst(); // lê e remove
// SET — no duplicates
Set<String> skus = new HashSet<>(Set.of("SKU-1", "SKU-2")); // mutável, sem ordem
Set<String> ordenado = new TreeSet<>(skus); // ordenação natural
Set<String> insercao = new LinkedHashSet<>(skus); // mantém ordem de inserção
skus.add("SKU-3");
skus.contains("SKU-1"); // true — O(1) em HashSet
// MAP — key-value, no duplicate keys
Map<String, Produto> catalogo = new HashMap<>();
catalogo.put("SKU-1", produto1);
catalogo.getOrDefault("SKU-99", produtoPadrao); // evita null
catalogo.putIfAbsent("SKU-1", outro); // não sobrescreve se existir
catalogo.computeIfAbsent("SKU-2", k -> buscarProduto(k)); // cria se ausente
catalogo.merge("SKU-1", novo, (velho, n) -> combinar(velho, n)); // merge com função
catalogo.forEach((sku, prod) -> System.out.println(sku + ": " + prod.nome()));
// Imutáveis — mais de 10 entradas usa Map.copyOf ou Map.ofEntries
Map<String, Integer> codigo = Map.ofEntries(
Map.entry("CONFIRMADO", 1),
Map.entry("CANCELADO", 0),
Map.entry("ENTREGUE", 2)
);
// Collections utility class
List<Integer> nums = new ArrayList<>(List.of(3,1,4,1,5,9,2,6));
int max = Collections.max(nums);
int freq = Collections.frequency(nums, 1); // quantas ocorrências de 1
Collections.unmodifiableList(nums); // wrapper imutávelStreams API
A Streams API (Java 8+) permite processar coleções de forma declarativa com pipelines funcionais. Streams são lazy — nada é processado até a operação terminal. Um stream só pode ser consumido uma vez.
List<Pedido> pedidos = pedidoRepo.findAll();
// Filtrar + mapear + coletar
List<String> idsPagos = pedidos.stream()
.filter(p -> p.status() == Status.PAGO) // operação intermediária (lazy)
.map(Pedido::id) // transforma cada elemento
.distinct() // remove duplicatas
.sorted() // ordenação natural
.toList(); // terminal imutável — Java 16+
// Ordenação customizada
List<Pedido> porTotal = pedidos.stream()
.sorted(Comparator.comparing(Pedido::total).reversed()
.thenComparing(Pedido::dataCriacao))
.toList();
// flatMap — transforma 1 elemento em N (List<List<Item>> → List<Item>)
List<Item> todosItens = pedidos.stream()
.flatMap(p -> p.itens().stream())
.filter(item -> item.quantidade() > 0)
.toList();
// Agregações
long totalPedidos = pedidos.stream().count();
BigDecimal receitaTotal = pedidos.stream()
.map(Pedido::total)
.reduce(BigDecimal.ZERO, BigDecimal::add);
Optional<Pedido> maiorPedido = pedidos.stream()
.max(Comparator.comparing(Pedido::total));
boolean todosPagos = pedidos.stream().allMatch(p -> p.status() == Status.PAGO);
boolean algumPago = pedidos.stream().anyMatch(p -> p.status() == Status.PAGO);
boolean nenhumPago = pedidos.stream().noneMatch(p -> p.status() == Status.PAGO);
Optional<Pedido> primeiro = pedidos.stream().filter(p -> p.total().compareTo(new BigDecimal("500")) > 0).findFirst();
// Collectors — groupingBy, partitioningBy, toMap
Map<Status, List<Pedido>> porStatus = pedidos.stream()
.collect(Collectors.groupingBy(Pedido::status));
Map<Status, Long> contagemPorStatus = pedidos.stream()
.collect(Collectors.groupingBy(Pedido::status, Collectors.counting()));
Map<Status, BigDecimal> totalPorStatus = pedidos.stream()
.collect(Collectors.groupingBy(Pedido::status,
Collectors.reducing(BigDecimal.ZERO, Pedido::total, BigDecimal::add)));
Map<Boolean, List<Pedido>> particionado = pedidos.stream()
.collect(Collectors.partitioningBy(p -> p.total().compareTo(new BigDecimal("200")) > 0));
// particionado.get(true) → pedidos acima de R$200
// particionado.get(false) → pedidos até R$200
Map<String, Pedido> porId = pedidos.stream()
.collect(Collectors.toMap(Pedido::id, p -> p)); // ARMADILHA: lança se houver id duplicado
Map<String, Pedido> porIdSafe = pedidos.stream()
.collect(Collectors.toMap(Pedido::id, p -> p, (p1, p2) -> p2)); // merge fn resolve duplicatas
// Joining — concatenar strings
String ids = pedidos.stream()
.map(Pedido::id)
.collect(Collectors.joining(", ", "[", "]")); // "[PED-1, PED-2, PED-3]"
// ARMADILHA: stream já consumido
Stream<Pedido> s = pedidos.stream();
s.filter(...).toList();
s.map(...).toList(); // IllegalStateException: stream already consumed — crie novo streamOptional
Optional<T> é um container que pode ou não conter um valor. Força o chamador a lidar explicitamente com a ausência em vez de receber null. Não é substituto geral de null — use apenas em retornos de método, nunca em campos ou parâmetros.
// Criação
Optional<Produto> encontrado = Optional.of(produto); // lança se null
Optional<Produto> talvez = Optional.ofNullable(valor); // aceita null → vazio
Optional<Produto> vazio = Optional.empty();
// Verificação e extração
encontrado.isPresent(); // true
vazio.isEmpty(); // Java 11+ — true
encontrado.get(); // retorna o valor — mas prefira os métodos abaixo
// Transformação funcional — a cadeia ideal
String nomeCliente = pedidoRepo.findById(id)
.map(Pedido::cliente) // Pedido → Cliente
.map(Cliente::nome) // Cliente → String
.filter(n -> !n.isBlank()) // descarta nomes em branco
.map(String::trim)
.orElse("Cliente não encontrado"); // valor padrão
// flatMap — quando o mapper já retorna Optional
Optional<Endereco> enderecoEntrega = pedidoRepo.findById(id)
.flatMap(p -> clienteRepo.findById(p.clienteId())) // retorna Optional<Cliente>
.flatMap(Cliente::enderecoEntrega); // retorna Optional<Endereco>
// Ações com efeito colateral
pedidoRepo.findById(id)
.ifPresent(p -> notificar(p.clienteId())); // só executa se presente
pedidoRepo.findById(id)
.ifPresentOrElse( // Java 9+
p -> processar(p),
() -> log.warn("Pedido {} não encontrado", id)
);
// Lançar exceção se vazio
Pedido pedido = pedidoRepo.findById(id)
.orElseThrow(() -> new PedidoNaoEncontradoException(id));
// orElseGet — lazy, só avalia o supplier se necessário (prefira sobre orElse para objetos pesados)
Pedido fallback = pedidoRepo.findById(id)
.orElseGet(() -> criarPedidoPadrao(id));
// ARMADILHAS
// 1. Nunca: if (opt.isPresent()) opt.get() — use map/orElse
// 2. Nunca: Optional como campo de classe (quebra serialização)
// 3. Nunca: Optional como parâmetro de método (api confusa)
// 4. Optional.of(null) lança NullPointerException imediatamenteLambdas e Functional Interfaces
Lambdas (Java 8+) são implementações inline de interfaces funcionais (interfaces com exatamente 1 método abstrato). O pacote java.util.function fornece as interfaces mais comuns. Composição de funções permite construir pipelines sem classes anônimas.
// Functional interfaces mais usadas
Function<Pedido, BigDecimal> calcular = Pedido::total;
Function<String, String> normalizar = s -> s.trim().toLowerCase();
Predicate<Pedido> isPago = p -> p.status() == Status.PAGO;
Consumer<String> logar = System.out::println;
Supplier<Pedido> novoPedido = () -> new Pedido(UUID.randomUUID().toString());
BiFunction<BigDecimal, Double, BigDecimal> aplicarDesconto =
(valor, pct) -> valor.multiply(BigDecimal.valueOf(1 - pct));
UnaryOperator<String> maiuscula = String::toUpperCase;
BinaryOperator<BigDecimal> somar = BigDecimal::add;
// Composição de Function
Function<String, String> pipeline = normalizar
.andThen(s -> s.replace(" ", "_")) // normalizar → depois substituir
.andThen(maiuscula); // → depois maiúscula
String resultado = pipeline.apply(" Rafael Marques "); // "RAFAEL_MARQUES"
// Composição de Predicate
Predicate<Pedido> isPagoEAcimaDe500 = isPago
.and(p -> p.total().compareTo(new BigDecimal("500")) > 0);
Predicate<Pedido> isPagoOuCancelado = isPago
.or(p -> p.status() == Status.CANCELADO);
Predicate<Pedido> naoEstaPago = isPago.negate();
// Uso prático em serviços
public List<Pedido> filtrar(List<Pedido> pedidos, Predicate<Pedido> criterio) {
return pedidos.stream().filter(criterio).toList();
}
filtrar(pedidos, isPagoEAcimaDe500);
filtrar(pedidos, p -> p.clienteId().equals("C-123"));
// Consumer encadeado
Consumer<Pedido> salvar = pedidoRepo::save;
Consumer<Pedido> notificar = p -> notificacaoService.enviar(p.clienteId(), "Pedido confirmado");
Consumer<Pedido> confirmar = salvar.andThen(notificar); // executa os dois em sequência
confirmar.accept(pedido);
// Supplier lazy — só avalia quando necessário
public Pedido buscarOuCriar(String id, Supplier<Pedido> criador) {
return pedidoRepo.findById(id).orElseGet(criador);
}
buscarOuCriar("PED-99", () -> new Pedido("PED-99", cliente, new BigDecimal("150.00")));Method References
Method references são uma forma concisa de lambdas que simplesmente delegam para um método existente. Existem quatro tipos: estático, de instância (de objeto específico), de instância (de tipo arbitrário) e construtor.
// 1. Referência para método estático — ClassName::staticMethod
List<String> ids = List.of("ped-1", "ped-2", "ped-3");
List<String> maiusculas = ids.stream()
.map(String::toUpperCase) // equivalente a s -> s.toUpperCase()
.toList();
Function<String, Integer> parser = Integer::parseInt; // s -> Integer.parseInt(s)
// 2. Referência para método de instância de objeto específico
PedidoRepository repo = new PedidoRepositoryImpl();
Optional<Pedido> encontrado = ids.stream()
.map(repo::findById) // id -> repo.findById(id)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
// 3. Referência para método de instância de tipo arbitrário
// — o primeiro argumento da lambda se torna o receptor
List<String> skus = List.of("SKU-C", "SKU-A", "SKU-B");
skus.sort(String::compareToIgnoreCase); // (a, b) -> a.compareToIgnoreCase(b)
List<Pedido> porId = pedidos.stream()
.sorted(Comparator.comparing(Pedido::getId)) // p -> p.getId()
.toList();
// 4. Referência para construtor — ClassName::new
Function<String, Pedido> criar = Pedido::new; // id -> new Pedido(id)
BiFunction<String, BigDecimal, Money> criarMoney = Money::new; // (a, c) -> new Money(a, c)
Supplier<ArrayList<Pedido>> listaFabrica = ArrayList::new; // () -> new ArrayList<>()
// Uso em streams
List<Money> precos = List.of("99.90", "49.99", "199.00").stream()
.map(BigDecimal::new)
.map(v -> new Money(v, "BRL")) // aqui a lambda é mais clara que constructor ref
.toList();Exception Handling
Java usa exceções verificadas (checked) e não-verificadas (unchecked). RuntimeException e seus filhos são unchecked — não precisam ser declarados ou capturados. try-with-resources garante fechamento de recursos mesmo com exceção.
// Hierarquia: Throwable → Error (não capture) / Exception → RuntimeException (unchecked)
// try-catch-finally clássico
public Pedido buscar(String id) {
Connection conn = null;
try {
conn = dataSource.getConnection();
return pedidoDao.findById(conn, id);
} catch (SQLException e) {
throw new RepositorioException("Erro ao buscar pedido " + id, e); // chained exception
} finally {
if (conn != null) {
try { conn.close(); } catch (SQLException ignored) {}
}
}
}
// try-with-resources — Java 7+ — AutoCloseable.close() chamado automaticamente
// ARMADILHA: declara cada recurso explicitamente — a Connection abaixo de getConnection()
// não seria fechada se declarada dentro do prepareStatement (connection leak!)
public List<Produto> importarCSV(Path arquivo) throws IOException, SQLException {
try (BufferedReader reader = Files.newBufferedReader(arquivo)) {
// reader fechado automaticamente, mesmo se houver exceção
return reader.lines()
.skip(1) // pula cabeçalho
.map(this::parseLinha)
.collect(Collectors.toList());
}
// DETALHE: se body lançar e close() também lançar, a exceção do close é "suprimida"
// acesse com catch (Exception e) { e.getSuppressed() }
}
// Multi-catch — Java 7+
try {
processar();
} catch (IllegalArgumentException | IllegalStateException e) {
log.warn("Argumento ou estado inválido: {}", e.getMessage());
} catch (IOException e) {
throw new ProcessamentoException("Falha de I/O", e);
}
// Exceção customizada
public class PedidoNaoEncontradoException extends RuntimeException {
private final String pedidoId;
public PedidoNaoEncontradoException(String pedidoId) {
super("Pedido não encontrado: " + pedidoId);
this.pedidoId = pedidoId;
}
// Construtor com causa — preserva stack trace original
public PedidoNaoEncontradoException(String pedidoId, Throwable causa) {
super("Pedido não encontrado: " + pedidoId, causa);
this.pedidoId = pedidoId;
}
public String getPedidoId() { return pedidoId; }
}Concorrência: Thread, Runnable, Callable, ExecutorService
Nunca crie threads diretamente com new Thread() em código de produção. Use ExecutorService para gerenciar pools de threads. Callable permite retornar valor e lançar exceção, diferente de Runnable.
// ExecutorService — pool fixo de threads
ExecutorService executor = Executors.newFixedThreadPool(4);
// Runnable — sem retorno
executor.execute(() -> processarPedido(pedidoId));
// Callable — com retorno, pode lançar exceção verificada
Callable<RelatorioFinanceiro> tarefa = () -> {
List<Pedido> pedidos = pedidoRepo.findByPeriodo(inicio, fim);
return gerarRelatorio(pedidos);
};
Future<RelatorioFinanceiro> futuro = executor.submit(tarefa);
// Aguardar resultado
try {
RelatorioFinanceiro relatorio = futuro.get(30, TimeUnit.SECONDS); // timeout
processar(relatorio);
} catch (TimeoutException e) {
futuro.cancel(true); // interrompe a thread
throw new TimeoutException("Relatório demorou mais de 30s");
} catch (ExecutionException e) {
throw new RuntimeException("Erro ao gerar relatório", e.getCause());
}
// ScheduledExecutorService — tarefas periódicas
ScheduledExecutorService agendador = Executors.newScheduledThreadPool(2);
agendador.scheduleAtFixedRate(
() -> sincronizarEstoque(),
0L, 5L, TimeUnit.MINUTES // inicial=0, período=5min
);
agendador.schedule(
() -> enviarRelatorioNocturno(),
calcularDelayAteMeiaNoite(), TimeUnit.SECONDS // uma vez no horário calculado
);
// Encerramento gracioso do executor
executor.shutdown(); // não aceita novas tarefas, aguarda as existentes
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow(); // força cancelamento
}CompletableFuture
CompletableFuture (Java 8+) permite compor operações assíncronas sem bloquear threads. thenApply é síncrono (executa na mesma thread), thenCompose encadeia outro future. Use o overload com Executor para controlar em qual pool a operação roda.
// Pipeline assíncrono simples
CompletableFuture<RespostaPagamento> resultado = CompletableFuture
.supplyAsync(() -> pedidoRepo.findById(pedidoId)) // IO async
.thenApply(pedido -> pricer.calcularComTaxas(pedido)) // CPU sync
.thenCompose(pedido -> pagamentoGateway.cobrarAsync(pedido)) // IO async (retorna CF)
.thenApply(cobranca -> new RespostaPagamento(cobranca.id(), cobranca.status()))
.exceptionally(ex -> {
log.error("Falha no pagamento", ex);
return RespostaPagamento.falha(ex.getMessage());
});
// thenCombine — combina dois futures independentes em paralelo
CompletableFuture<Cliente> clienteFuture = clienteRepo.findByIdAsync(clienteId);
CompletableFuture<Endereco> enderecoFuture = enderecoService.buscarAsync(clienteId);
CompletableFuture<String> etiqueta = clienteFuture.thenCombine(
enderecoFuture,
(cliente, endereco) -> cliente.nome() + "\n" + endereco.linha1() + "\n" + endereco.cidade()
);
// allOf — aguarda todos (sem retorno de valores, por design)
CompletableFuture<Void> todos = CompletableFuture.allOf(f1, f2, f3);
todos.thenRun(() -> System.out.println("Todos concluídos"))
.join(); // bloqueia thread atual até completar — use com cuidado
// anyOf — retorna o primeiro que completar
CompletableFuture<Object> primeiro = CompletableFuture.anyOf(f1, f2, f3);
// handle — trata tanto sucesso quanto falha
CompletableFuture<String> comHandle = resultado.handle((res, ex) -> {
if (ex != null) return "Erro: " + ex.getMessage();
return "Sucesso: " + res.id();
});
// Execução em pool específico
ExecutorService ioPool = Executors.newFixedThreadPool(20);
CompletableFuture.supplyAsync(() -> chamarAPI(), ioPool)
.thenApplyAsync(r -> transformar(r), ForkJoinPool.commonPool())
.thenAcceptAsync(r -> salvar(r), ioPool);Virtual Threads (Java 21)
Virtual Threads (Project Loom) são threads ultralevados gerenciados pela JVM, não pelo SO. Permitem escrever código bloqueante com escalabilidade de código reativo. Criação barata: milhões de virtual threads são viáveis.
// Criação direta de virtual thread
Thread vt = Thread.ofVirtual()
.name("processador-pedido-", 0) // nome base + contador
.start(() -> processarPedido(pedidoId));
vt.join();
// Thread.Builder — reutilizável
Thread.Builder.OfVirtual builder = Thread.ofVirtual().name("tarefa-", 0);
Thread t1 = builder.start(() -> tarefa1());
Thread t2 = builder.start(() -> tarefa2());
// ExecutorService com virtual threads — drop-in replacement para pools
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<RelatorioItem>> futures = itens.stream()
.map(item -> executor.submit(() -> processarItem(item))) // uma thread por tarefa
.toList();
for (Future<RelatorioItem> f : futures) {
resultados.add(f.get());
}
} // fecha o executor ao sair do try
// Virtual threads são ideais para I/O — cada requisição HTTP pode ter sua própria thread
// Em Spring Boot 3.2+: spring.threads.virtual.enabled=true
// ARMADILHA: synchronized + virtual thread = "pinning"
// Se um virtual thread bloqueia dentro de synchronized, a carrier thread (OS thread) fica presa
// Solução: substituir synchronized por ReentrantLock
public class ContadorSeguro {
private int valor = 0;
private final ReentrantLock lock = new ReentrantLock(); // sem pinning
public void incrementar() {
lock.lock();
try {
valor++;
} finally {
lock.unlock();
}
}
}
// ARMADILHA: não use ThreadLocal com virtual threads em pool — pode vazar entre tarefas
// Prefira ScopedValue (Java 21, preview) para valores de escopo de tarefaStructured Concurrency (Java 21)
StructuredTaskScope (Java 21, ainda em --enable-preview em algumas JVMs) organiza tarefas concorrentes com escopo definido: todas as subtarefas terminam antes de sair do bloco. Simplifica cancel-on-failure e short-circuit.
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;
// ShutdownOnFailure — cancela tudo se qualquer subtarefa falhar
public record ResumoCliente(Cliente cliente, List<Pedido> pedidos, Saldo saldo) {}
public ResumoCliente buscarResumoConcorrente(String clienteId) throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Subtask<Cliente> subCliente = scope.fork(() -> clienteRepo.findById(clienteId));
Subtask<List<Pedido>> subPedidos = scope.fork(() -> pedidoRepo.findByCliente(clienteId));
Subtask<Saldo> subSaldo = scope.fork(() -> financeiroService.saldo(clienteId));
scope.join() // aguarda todas as subtarefas
.throwIfFailed(); // propaga exceção se alguma falhou
// Aqui todas as subtarefas completaram com sucesso
return new ResumoCliente(subCliente.get(), subPedidos.get(), subSaldo.get());
} // escopo fechado: qualquer subtarefa ainda rodando é cancelada
}
// ShutdownOnSuccess — retorna o primeiro resultado bem-sucedido (short-circuit)
public Produto buscarDeCacheOuBanco(String sku) throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<Produto>()) {
scope.fork(() -> cache.get(sku)); // tenta cache primeiro
scope.fork(() -> produtoRepo.find(sku)); // ou banco
scope.join();
return scope.result(); // retorna o primeiro que completou
}
}synchronized, volatile, Lock, ReentrantLock, Semaphore, Latch, Barrier
Java oferece múltiplos mecanismos de sincronização. synchronized é o mais simples mas tem limitações (não pode ser interrompido, não suporta timeout, causa pinning em virtual threads). java.util.concurrent.locks oferece controle fino.
// Race condition — ERRADO: dois threads lendo e escrevendo sem sincronização
public class ContadorErrado {
private int valor = 0;
public void incrementar() { valor++; } // NÃO é atômico! lê-incrementa-escreve
}
// volatile — garante visibilidade entre threads, NÃO atomicidade
public class Flag {
private volatile boolean ativo = false; // sem volatile, JIT pode cachear em registro
public void parar() { ativo = false; }
public boolean ativo() { return ativo; }
}
// AtomicInteger — operações atômicas sem lock
private final AtomicInteger contador = new AtomicInteger(0);
contador.incrementAndGet();
contador.compareAndSet(esperado, novo); // CAS — Compare-And-Set
// synchronized em método — lock no objeto this
public synchronized void transferir(ContaBancaria destino, BigDecimal valor) {
if (saldo.compareTo(valor) < 0) throw new SaldoInsuficienteException();
saldo = saldo.subtract(valor);
destino.depositar(valor);
}
// ReentrantLock — mais flexível que synchronized
public class Estoque {
private int quantidade;
private final ReentrantLock lock = new ReentrantLock();
public boolean reservar(int qtd) {
lock.lock();
try {
if (quantidade < qtd) return false;
quantidade -= qtd;
return true;
} finally {
lock.unlock(); // sempre no finally!
}
}
}
// ReadWriteLock — múltiplos leitores simultâneos, escritor exclusivo
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public Produto ler(String sku) { rwLock.readLock().lock(); try { return cache.get(sku); } finally { rwLock.readLock().unlock(); } }
public void atualizar(String sku, Produto p) { rwLock.writeLock().lock(); try { cache.put(sku, p); } finally { rwLock.writeLock().unlock(); } }
// Semaphore — controla número de acessos concorrentes
Semaphore conexoes = new Semaphore(10); // máximo 10 conexões simultâneas
conexoes.acquire();
try { /* usa conexão */ } finally { conexoes.release(); }
// CountDownLatch — aguarda N tarefas completarem
CountDownLatch latch = new CountDownLatch(3);
executor.submit(() -> { importarProdutos(); latch.countDown(); });
executor.submit(() -> { importarClientes(); latch.countDown(); });
executor.submit(() -> { importarPedidos(); latch.countDown(); });
latch.await(60, TimeUnit.SECONDS); // bloqueia até as 3 contagens zerarem
// CyclicBarrier — sincroniza N threads em um ponto de encontro (reutilizável)
CyclicBarrier barreira = new CyclicBarrier(3, () -> log.info("Rodada concluída"));
// cada thread chama barreira.await() e aguarda as outras 2 chegaremColeções Concorrentes
Coleções do pacote java.util não são thread-safe por padrão. java.util.concurrent oferece alternativas otimizadas que evitam bloqueio total com estratégias como lock por segmento, copy-on-write e operações não-bloqueantes.
// ConcurrentHashMap — seguro e performático para leitura/escrita concorrente
ConcurrentHashMap<String, Produto> cache = new ConcurrentHashMap<>();
cache.put("SKU-1", produto);
cache.putIfAbsent("SKU-1", outro); // atômico — não sobrescreve
cache.computeIfAbsent("SKU-2", // atômico — computa se ausente
sku -> produtoRepo.findBySku(sku));
cache.compute("SKU-1", // atômico — lê-transforma-escreve
(sku, prod) -> prod == null ? novo : atualizar(prod));
// ARMADILHA: ConcurrentHashMap NÃO aceita null como chave ou valor
// HashMap aceita null, ConcurrentHashMap lança NullPointerException!
// CopyOnWriteArrayList — otimizada para leitura frequente, escrita rara
// Cada escrita cria cópia interna — iteradores nunca lançam ConcurrentModificationException
CopyOnWriteArrayList<EventListener> listeners = new CopyOnWriteArrayList<>();
listeners.add(novoListener);
for (EventListener l : listeners) { // safe mesmo se outro thread remover durante iteração
l.onEvent(evento);
}
// BlockingQueue — produtor-consumidor sem polling
BlockingQueue<Pedido> fila = new LinkedBlockingQueue<>(1000); // capacidade máxima
// Thread produtora
Runnable produtor = () -> {
while (rodando) {
Pedido pedido = receberDaAPI();
fila.put(pedido); // bloqueia se fila cheia — não desperdiça CPU
}
};
// Thread consumidora
Runnable consumidor = () -> {
while (rodando || !fila.isEmpty()) {
Pedido pedido = fila.poll(1, TimeUnit.SECONDS); // bloqueia até 1s
if (pedido != null) processar(pedido);
}
};
// ArrayBlockingQueue vs LinkedBlockingQueue
// Array: tamanho fixo, menor overhead, contiguous memory
// Linked: tamanho dinâmico (ou bounded), separates put/take locks → maior throughput
// PriorityBlockingQueue — processa por prioridade
PriorityBlockingQueue<Tarefa> prioridades = new PriorityBlockingQueue<>();
// Tarefa deve implementar Comparable ou passar ComparatorI/O Moderno: Files, Path, BufferedReader
A API java.nio.file (Java 7+) modernizou o I/O com Path, Files e streams de linhas. Files.readString e Files.writeString (Java 11+) simplificam leitura/escrita de arquivos inteiros.
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
// Leitura simples — arquivo inteiro em memória
String conteudo = Files.readString(Path.of("/dados/pedidos.json"));
List<String> linhas = Files.readAllLines(Path.of("/dados/pedidos.csv"), StandardCharsets.UTF_8);
// Leitura lazy — ideal para arquivos grandes
try (Stream<String> stream = Files.lines(Path.of("/logs/app.log"))) {
long erros = stream
.filter(linha -> linha.contains("ERROR"))
.count();
}
// Escrita
Files.writeString(Path.of("/saida/relatorio.txt"), conteudo);
Files.writeString(Path.of("/saida/relatorio.txt"), conteudo, StandardOpenOption.APPEND);
Files.write(Path.of("/saida/dados.bin"), bytes);
// BufferedReader — controle fino com buffer
try (BufferedReader reader = Files.newBufferedReader(Path.of("/dados/large.csv"))) {
String header = reader.readLine(); // primeira linha
String linha;
while ((linha = reader.readLine()) != null) {
processarLinha(linha);
}
}
// Path operations
Path base = Path.of("/app/dados");
Path arquivo = base.resolve("pedidos/2024.csv"); // /app/dados/pedidos/2024.csv
Path relativo = base.relativize(arquivo); // pedidos/2024.csv
arquivo.getFileName(); // 2024.csv
arquivo.getParent(); // /app/dados/pedidos
arquivo.toAbsolutePath();
Files.exists(arquivo);
Files.isDirectory(base);
Files.size(arquivo); // bytes
Files.getLastModifiedTime(arquivo).toInstant();
// Copiar e mover
Files.copy(origem, destino, StandardCopyOption.REPLACE_EXISTING);
Files.move(origem, destino, StandardCopyOption.ATOMIC_MOVE);
// Caminhar pela árvore de diretórios
try (Stream<Path> walk = Files.walk(base)) {
List<Path> csvs = walk
.filter(p -> p.toString().endsWith(".csv"))
.toList();
}
// Criar diretórios
Files.createDirectories(Path.of("/app/dados/exportacoes/2024")); // cria toda a hierarquiaSerialização e Records
Java serialization (via Serializable) persiste o estado de objetos. Records são serializáveis mas com semântica especial: a desserialização passa pelo construtor canônico, garantindo que as validações do construtor compacto sejam executadas.
// Serialização tradicional
public class ConfiguracaoPagamento implements Serializable {
private static final long serialVersionUID = 1L; // versão do schema — importante!
private String gateway;
private String apiKey;
private transient HttpClient httpClient; // transient: não serializado (não-serializável ou sensitivo)
// readObject/writeObject customizados
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // serializa campos não-transient
oos.writeUTF(encrypt(apiKey)); // serializa apiKey criptografada
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
this.apiKey = decrypt(ois.readUTF()); // descriptografa ao ler
this.httpClient = HttpClient.newHttpClient(); // reconstrói transient
}
}
// Record + Serializable — construtor canônico SEMPRE executado na desserialização
public record ConfiguracaoApp(String host, int porta, boolean tls) implements Serializable {
public ConfiguracaoApp {
if (porta < 1 || porta > 65535)
throw new IllegalArgumentException("Porta inválida: " + porta);
// Esta validação RODA na desserialização — protege contra dados corrompidos
}
}
// Serialização com JSON (Jackson) — mais comum em produção
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(pedido); // serializa
Pedido lido = mapper.readValue(json, Pedido.class); // desserializa
// Records funcionam bem com Jackson — sem getters tradicionais necessários
// com jackson-module-parameter-names ou construtor @JsonCreatorModules (JPMS) — module-info.java
O Java Platform Module System (JPMS, Java 9+) divide o código em módulos com dependências e exportações explícitas. Melhora encapsulamento, startup time e permite imagens nativas menores (jlink).
// module-info.java — deve ficar na raiz do source root (src/main/java/)
module com.empresa.pedidos {
// Dependências obrigatórias
requires java.base; // implícito — sempre disponível
requires java.sql; // JDBC
requires com.fasterxml.jackson.databind; // Jackson
requires static lombok; // apenas em tempo de compilação (opcional em runtime)
requires transitive com.empresa.shared; // quem depende de mim herda essa dependência
// O que este módulo expõe
exports com.empresa.pedidos.api; // pacote público
exports com.empresa.pedidos.domain to com.empresa.relatorios; // exportação qualificada
// Acesso via reflection (ex: Hibernate, Jackson, Spring)
opens com.empresa.pedidos.domain to com.fasterxml.jackson.databind;
opens com.empresa.pedidos.infra; // abre para todos (reflection irrestrita)
// Service Provider Interface
uses com.empresa.shared.GatewayPagamento; // consome este serviço
provides com.empresa.shared.GatewayPagamento // implementa este serviço
with com.empresa.pedidos.infra.StripeGateway;
}
// Comandos úteis
// Compilar com módulos:
// javac --module-path mods -d out --module-source-path src $(find . -name "*.java")
//
// Listar módulos do JDK:
// java --list-modules
//
// Descrever um módulo:
// java --describe-module java.sql
//
// Gerar imagem customizada (só o necessário):
// jlink --module-path $JAVA_HOME/jmods:mods \
// --add-modules com.empresa.pedidos \
// --output dist/runtime \
// --strip-debug --compress=2Versões — Java 8 a 21
As versões LTS (Long-Term Support) são as recomendadas para produção: Java 8, 11, 17 e 21. As versões intermediárias têm suporte de apenas 6 meses.
| Versão | Ano | LTS | Principais recursos |
|---|---|---|---|
| 8 | 2014 | Sim | Lambdas e method references, Streams API, Optional, default/static em interfaces, java.time (LocalDate/Instant), CompletableFuture, Nashorn JS engine |
| 9 | 2017 | Não | JPMS (módulos), JShell REPL, List.of/Map.of/Set.of, Stream.takeWhile/dropWhile, Optional.ifPresentOrElse, HTTP/2 Client (incubator) |
| 10 | 2018 | Não | var (inferência de tipo local), List.copyOf, Graal JIT experimental |
| 11 | 2018 | Sim | var em lambdas, String.isBlank/strip/lines/repeat, HTTP Client estável, Files.readString/writeString, remoção Java EE (javax.*) e Nashorn |
| 12 | 2019 | Não | Switch expression (preview), Shenandoah GC (experimental) |
| 13 | 2019 | Não | Text blocks (preview), Switch expression (2ª preview) |
| 14 | 2020 | Não | Switch expression estável, Records (preview), instanceof pattern (preview), NPE mensagens melhoradas |
| 15 | 2020 | Não | Text blocks estáveis, Sealed classes (preview), Records (2ª preview) |
| 16 | 2021 | Não | Records estáveis, instanceof pattern estável, Stream.toList(), Vector API (incubator) |
| 17 | 2021 | Sim | Sealed classes estáveis, Pattern matching instanceof estável, Text blocks estáveis, Switch expression estável, Foreign Function & Memory API (incubator), remoção RMI Activation |
| 18 | 2022 | Não | UTF-8 como charset padrão, Simple Web Server (jwebserver), Javadoc code snippets, Vector API (3ª incubator) |
| 19 | 2022 | Não | Virtual threads (preview), Structured Concurrency (incubator), Record Patterns (preview), Foreign Function & Memory (2ª preview) |
| 20 | 2023 | Não | Virtual threads (2ª preview), Scoped Values (incubator), Pattern matching switch (4ª preview) |
| 21 | 2023 | Sim | Virtual Threads estáveis, Structured Concurrency (preview), Record Patterns estáveis, Pattern matching switch estáveis, Sequenced Collections (getFirst/getLast), String Templates (preview — JEP 430), Generational ZGC |
| 25 | 2025 | Sim | (LTS em andamento) Primitive types in patterns, Module imports, Flexible constructor bodies estáveis, Stream.gather, String Templates (esperado estável) |