Backend

Java

Referência completa de Java do básico ao avançado — tipos, coleções, streams, generics, records, sealed classes, pattern matching, concorrência, virtual threads e mais

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ática

String, 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.00

Generics: 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 runtime

Records (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 gerado

Sealed 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ático

Enums — 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ável

Streams 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 stream

Optional

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 imediatamente

Lambdas 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 tarefa

Structured 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 chegarem

Coleçõ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 Comparator

I/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 hierarquia

Serializaçã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 @JsonCreator

Modules (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=2

Versõ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ãoAnoLTSPrincipais recursos
82014SimLambdas e method references, Streams API, Optional, default/static em interfaces, java.time (LocalDate/Instant), CompletableFuture, Nashorn JS engine
92017NãoJPMS (módulos), JShell REPL, List.of/Map.of/Set.of, Stream.takeWhile/dropWhile, Optional.ifPresentOrElse, HTTP/2 Client (incubator)
102018Nãovar (inferência de tipo local), List.copyOf, Graal JIT experimental
112018Simvar em lambdas, String.isBlank/strip/lines/repeat, HTTP Client estável, Files.readString/writeString, remoção Java EE (javax.*) e Nashorn
122019NãoSwitch expression (preview), Shenandoah GC (experimental)
132019NãoText blocks (preview), Switch expression (2ª preview)
142020NãoSwitch expression estável, Records (preview), instanceof pattern (preview), NPE mensagens melhoradas
152020NãoText blocks estáveis, Sealed classes (preview), Records (2ª preview)
162021NãoRecords estáveis, instanceof pattern estável, Stream.toList(), Vector API (incubator)
172021SimSealed 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
182022NãoUTF-8 como charset padrão, Simple Web Server (jwebserver), Javadoc code snippets, Vector API (3ª incubator)
192022NãoVirtual threads (preview), Structured Concurrency (incubator), Record Patterns (preview), Foreign Function & Memory (2ª preview)
202023NãoVirtual threads (2ª preview), Scoped Values (incubator), Pattern matching switch (4ª preview)
212023SimVirtual 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
252025Sim(LTS em andamento) Primitive types in patterns, Module imports, Flexible constructor bodies estáveis, Stream.gather, String Templates (esperado estável)