Especialização em Sistemas com Java 21 LTS • Módulo de Fundamentos


🗺️ Mapa Conceitual do Tópico

flowchart TD
    A["Fluxo de Execução"] --> B{"Expressão Booleana?"}
    B -->|true| C["Ramo Verdadeiro (if / then)"]
    B -->|false| D["Ramo Falso (else / default)"]
    
    subgraph MECANISMOS["Evolução das Estruturas Condicionais"]
        E["if-else & Ternário ?:"]
        F["Switch Tradicional (break / fall-through)"]
        G["Switch Expressions (-> e yield)"]
        H["Pattern Matching com Guardas (when)"]
    end
    
    subgraph JVM["Otimizações no Bytecode"]
        I["Branch Prediction CPU"]
        J["tableswitch (O(1) Jump Table)"]
        K["lookupswitch (O(log n) Binary Search)"]
    end
    
    C & D --> MECANISMOS
    MECANISMOS --> JVM
    
    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style G fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style H fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

🔀 1. Lógica Condicional e Avaliação de Curto-Circuito

No Java, qualquer estrutura condicional if exige estritamente um tipo boolean primitivo ou sua wrapper java.lang.Boolean. Ao contrário de linguagens como C ou JavaScript, inteiros não são convertidos implicitamente em booleanos (if (1) resulta em erro de compilação).

Operadores Lógicos e Avaliação de Curto-Circuito:

  • Curto-Circuito Lógico (&& e ||): Na conjunção A && B, se A for false, B nunca é avaliado. Na disjunção A || B, se A for true, B é ignorado. Isso previne falhas graves como acessar métodos em ponteiros nulos:
    if (cliente != null && cliente.isAtivo()) { ... } // Seguro contra NullPointerException
    
  • Operadores Bitwise em Booleanos (& e |): Forçam a avaliação de ambos os operandos, independentemente do resultado do primeiro. Devem ser evitados em desvios condicionais devido à perda de performance e risco de exceção.

⚖️ 2. Operador Ternário e Regras de Tipo do Compilador

O operador ternário (condicao ? expr1 : expr2) é uma expressão que produz um valor direto. Seu tipo de retorno é inferido pelo compilador com base no menor supertipo comum entre expr1 e expr2.

Armadilha Crítica: Auto-unboxing e NullPointerException

Quando se misturam tipos primitivos e wrappers num operador ternário, a JVM aplica regras de promoção numérica da JLS (Java Language Specification), forçando o unboxing:

// PERIGO: Lança NullPointerException em tempo de execução!
Integer valorNulo = null;
Double resultado = (flag) ? 1.0 : valorNulo; // valorNulo sofre unboxing para double!

🎯 3. Switch Expressions Modernas (Java 14/17/21)

O Java clássico tratava o switch como uma instrução imperativa sujeita ao infame erro de fall-through (esquecimento da palavra-chave break). Desde o Java 14 e consolidado no Java 21 LTS, o switch pode ser utilizado como uma expressão que retorna valor:

1. Sintaxe com Seta (->)

Elimina completamente o fall-through e permite múltiplos rótulos (multi-labels) separados por vírgula:

String tipoDia = switch (diaSemana) {
    case 1, 7 -> "Fim de Semana";
    case 2, 3, 4, 5, 6 -> "Dia Comercial";
    default -> throw new IllegalArgumentException("Dia inválido: " + diaSemana);
};

2. A Palavra-Chave yield

Quando um ramo do switch necessita de um bloco multi-linhas de instruções (cálculos, logs, validações), utiliza-se yield para produzir o valor resultante do bloco:

double tarifa = switch (categoria) {
    case "PREMIUM" -> 0.05;
    case "STANDARD" -> {
        logger.info("Aplicando cálculo dinâmico para Standard");
        double base = calcularTaxaBase();
        yield base * 1.15;
    }
    default -> 0.20;
};

🛡️ 4. Pattern Matching para Switch e Cláusulas when (Java 21 LTS)

O Java 21 estabilizou o Pattern Matching para Switch (JEP 441), permitindo alternar fluxos com base no tipo dinâmico do objeto, extrair a variável local tipada sem casting explícito e incluir filtros booleanos refinados com a cláusula de guarda when:

public static String formatarDado(Object obj) {
    return switch (obj) {
        case null -> "Elemento nulo";
        case Integer i when i < 0 -> String.format("Inteiro negativo: (%d)", i);
        case Integer i -> String.format("Inteiro positivo: +%d", i);
        case Long l -> String.format("Long: %d L", l);
        case String s when s.isBlank() -> "String vazia";
        case String s -> "Texto: " + s.trim();
        default -> "Objeto desconhecido: " + obj.getClass().getSimpleName();
    };
}

📊 5. Matriz Comparativa de Estruturas de Decisão

Recurso if-else if-else switch Clássico switch Expression Pattern Matching switch
Retorno de Expressão Não (somente statements) Não Sim (-> ou yield) Sim
Risco de Fall-through Inexistente Alto (exige break) Nenhum Nenhum
Garantia de Exaustividade Manual (via else) Nenhuma Garantida pelo Compilador Garantida pelo Compilador
Suporte a null Exige checagem explícita Lança NPE imediatamente Lança NPE (a menos de case null) Suporte Nativo (case null)
Guardas Refinadas Operadores &&, || Não suportado Não suportado Cláusula when
Bytecode Subjacente if_icmp*, goto tableswitch / lookupswitch Otimizado via invokedynamic typeSwitch via Bootstrap

💻 6. Código de Demonstração Corporativo (Java 21 LTS)

package com.empresa.fundamentos;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * Motor de análise de crédito e fraude com Java 21 Switch Expressions e Pattern Matching.
 */
public class MotorDecisaoCredito {

    public sealed interface Solicitacao permits EmprestimoPessoal, FinanciamentoImobiliario, CartaoCredito {}

    public record EmprestimoPessoal(BigDecimal valor, int parcelas, int scoreSerasa) implements Solicitacao {}
    public record FinanciamentoImobiliario(BigDecimal valor, BigDecimal rendaMensal, boolean temGarantia) implements Solicitacao {}
    public record CartaoCredito(BigDecimal limiteSolicitado, boolean clienteAntigo) implements Solicitacao {}

    public enum Parecer { APROVADO, REPROVADO, ANALISE_HUMANA }

    public static Parecer avaliarProposta(Solicitacao proposta) {
        return switch (proposta) {
            case null -> Parecer.REPROVADO;

            // Pattern matching com cláusula de guarda (when)
            case EmprestimoPessoal ep when ep.scoreSerasa() >= 750 && ep.parcelas() <= 36 -> Parecer.APROVADO;
            case EmprestimoPessoal ep when ep.scoreSerasa() < 400 -> Parecer.REPROVADO;
            case EmprestimoPessoal ep -> Parecer.ANALISE_HUMANA;

            case FinanciamentoImobiliario fi when fi.temGarantia() -> {
                BigDecimal parcelaEstimada = fi.valor().divide(new BigDecimal("360"), 2, RoundingMode.HALF_UP);
                BigDecimal comprometimento = parcelaEstimada.divide(fi.rendaMensal(), 2, RoundingMode.HALF_UP);
                yield (comprometimento.compareTo(new BigDecimal("0.30")) <= 0) ? Parecer.APROVADO : Parecer.ANALISE_HUMANA;
            }
            case FinanciamentoImobiliario fi -> Parecer.REPROVADO;

            case CartaoCredito cc when cc.clienteAntigo() || cc.limiteSolicitado().compareTo(new BigDecimal("5000.00")) <= 0 -> 
                Parecer.APROVADO;
            case CartaoCredito cc -> Parecer.ANALISE_HUMANA;
        };
    }

    public static void main(String[] args) {
        Solicitacao proposta1 = new EmprestimoPessoal(new BigDecimal("25000.00"), 24, 820);
        Solicitacao proposta2 = new FinanciamentoImobiliario(new BigDecimal("450000.00"), new BigDecimal("12000.00"), true);
        Solicitacao proposta3 = null;

        System.out.printf("Proposta 1 (Empréstimo)   : %s%n", avaliarProposta(proposta1));
        System.out.printf("Proposta 2 (Imobiliário)  : %s%n", avaliarProposta(proposta2));
        System.out.printf("Proposta 3 (Nula)         : %s%n", avaliarProposta(proposta3));
    }
}