Java8 min de leitura

Programação na era IA

Guia de Sobrevivência: Codificação Inteligente na Era da IA

Com o surgimento do "vibe coding" e do uso em massa de assistentes como GitHub Copilot, ChatGPT, Claude Code e Cursor, a produtividade aumentou, mas o risco de gerar código ineficiente, verboso e com arquitetura falha também cresceu.

Abaixo, compilei as principais lições para manter a qualidade do código Java, garantindo que o "humano no loop" sempre traga a melhor performance e legibilidade.

1. Otimização de Strings: Fuja da Concatenação em Loops

Strings em Java são imutáveis. Toda vez que você concatena strings (usando +), o Java cria um novo objeto. Em loops, isso gera um volume imenso de "lixo" para o Garbage Collector limpar, degradando a performance para algo próximo de O(*N^*2).

❌ O que a IA pode sugerir (Ineficiente):

String resultado = "";
for (String s : lista) {
    resultado += s; // Cria um novo objeto a cada iteração!
}

✅ A Boa Prática (Eficiente): Use o StringBuilder. Ele é mutável e muito mais rápido para modificações frequentes.

StringBuilder sb = new StringBuilder();
for (String s : lista) {
    sb.append(s);
}
String resultado = sb.toString();

2. Adeus à "Pirâmide do Destino" (Null Checks) Evite o aninhamento excessivo de verificações de nulidade para acessar propriedades de objetos (ex: Pedido -> Cliente -> Endereço -> Cidade). Isso torna o código feio e difícil de manter.

❌ Exemplo "Pirâmide do Destino":

if (pedido != null) {
    if (pedido.getCliente() != null) {
        if (pedido.getCliente().getEndereco() != null) {
            return pedido.getCliente().getEndereco().getCidade();
        }
    }
}

✅ A Boa Prática: Utilize Optional.ofNullable com map. É mais legível, funcional e seguro.

return Optional.ofNullable(pedido)
    .map(Pedido::getCliente)
    .map(Cliente::getEndereco)
    .map(Endereco::getCidade)
    .orElse("Desconhecida");

3. Cuidado com Boxing e Unboxing em Massa

Misturar tipos primitivos (int) com objetos (Integer) dentro de loops adiciona um overhead desnecessário de processamento devido às conversões constantes.

❌ Problema de Overhead:

 Integer soma = 0;
for (int i = 0; i < lista.size(); i++) {
    soma += lista.get(i); // Desempacota Integer, soma, e empacota de novo
}

✅ A Boa Prática: Mantenha o processamento em tipos primitivos o máximo possível ou use Streams otimizadas internamente.


4. Expressões Regulares (Regex): Compile uma Única Vez

Compilar um padrão de Regex é uma operação muito cara. Nunca coloque a compilação dentro de um loop.

❌ Erro Comum:

for (String texto : lista) {
    if (Pattern.matches("^[1-7, 10, 11]+$", texto)) { // Compila a regex a cada volta!
        // ...
    }
}

✅ A Boa Prática: Defina a Regex como uma constante estática final (private static final Pattern) e reuse-a.

private static final Pattern NUMEROS = Pattern.compile("^[1-7, 10, 11]+$");

// No loop:
NUMEROS.matcher(texto).matches();

5. Deixe o Compilador JIT Trabalhar por Você Nem toda micro-otimização precisa ser feita manualmente. O JIT (Just-In-Time) Compiler do Java é excelente em:

  • Inlining de Métodos: Se você tem um método pequeno chamado milhares de vezes, o JIT "copia e cola" o código do método diretamente onde ele é chamado para evitar o custo de empilhar chamadas na memória.
  • Código Invariante: O JIT consegue identificar códigos dentro de loops que não mudam de valor e movê-los para fora automaticamente.

6. Sincronização Inteligente

Evite sincronizar métodos inteiros, pois isso pode travar threads desnecessariamente e prejudicar a concorrência do app.

  • Dica: Sincronize apenas o bloco crítico de código ou use classes atômicas como AtomicInteger, que já são thread-safe por natureza.

7. Aproveite os Virtual Threads (Java 21+)

O Java 21 introduziu Virtual Threads (Project Loom), que permitem criar milhares ou até milhões de threads leves sem sobrecarregar o sistema operacional. Isso é especialmente útil para aplicações I/O-bound (ex: requisições HTTP, acesso a banco de dados).

✅ A Boa Prática: Use Executors.newVirtualThreadPerTaskExecutor() para criar pools de virtual threads ao invés de threads tradicionais do sistema operacional.

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 10_000; i++) {
        executor.submit(() -> {
            // Tarefa I/O-bound aqui
        });
    }
}

8. Pattern Matching e Switch Expressions Modernos

Desde o Java 17, e aprimorado no Java 21, o pattern matching em switch permite código mais limpo e seguro, eliminando muitos instanceof e casts desnecessários.

❌ Forma Antiga:

if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.toUpperCase());
} else if (obj instanceof Integer) {
    Integer i = (Integer) obj;
    System.out.println(i * 2);
}

✅ A Boa Prática (Java 21+):

switch (obj) {
    case String s -> System.out.println(s.toUpperCase());
    case Integer i -> System.out.println(i * 2);
    case null -> System.out.println("Valor nulo");
    default -> System.out.println("Tipo desconhecido");
}

9. Record Classes para DTOs Imutáveis

Os Records (Java 14+) são classes imutáveis compactas, perfeitas para Data Transfer Objects (DTOs). Eles eliminam boilerplate de getters, equals, hashCode e toString.

✅ A Boa Prática:

public record Cliente(String nome, String email, int idade) {}

// Uso:
Cliente cliente = new Cliente("João", "joao@email.com", 30);
System.out.println(cliente.nome()); // Acesso direto

⚠️ Cuidado: Records são imutáveis por design. Se precisar de mutabilidade, use classes tradicionais.


10. Sealed Classes para Hierarquias Controladas

As Sealed Classes (Java 17+) permitem que você restrinja quais classes podem estender ou implementar uma interface/classe, melhorando a segurança de tipo e facilitando pattern matching exaustivo.

public sealed interface Forma permits Circulo, Retangulo, Triangulo {}

public final class Circulo implements Forma { /* ... */ }
public final class Retangulo implements Forma { /* ... */ }
public final class Triangulo implements Forma { /* ... */ }

✅ Benefício: O compilador pode verificar que todos os casos foram cobertos em um switch, evitando bugs de casos não tratados.


11. Structured Concurrency (Preview no Java 21+)

A Structured Concurrency organiza tarefas concorrentes em uma estrutura hierárquica, facilitando o tratamento de erros e cancelamento de tarefas relacionadas.

✅ A Boa Prática: Use StructuredTaskScope para gerenciar múltiplas tarefas concorrentes de forma mais previsível.

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    var tarefa1 = scope.fork(() -> buscarDados1());
    var tarefa2 = scope.fork(() -> buscarDados2());
    
    scope.join();
    scope.throwIfFailed();
    
    var resultado1 = tarefa1.get();
    var resultado2 = tarefa2.get();
}

12. Text Blocks para Strings Multilinha

Os Text Blocks (Java 15+) tornam muito mais fácil trabalhar com strings multilinha, como JSON, SQL, HTML, ou XML, sem concatenações feias.

❌ Forma Antiga:

String json = "{\n" +
              "  \"nome\": \"João\",\n" +
              "  \"idade\": 30\n" +
              "}";

✅ A Boa Prática:

String json = """
    {
      "nome": "João",
      "idade": 30
    }
    """;

13. Evite Stream em Contextos de Alta Performance

Embora Streams sejam elegantes e funcionais, eles adicionam overhead em cenários de alta performance devido à criação de objetos intermediários e boxing/unboxing.

✅ A Boa Prática: Para loops críticos de performance com milhões de iterações, considere usar loops tradicionais (for) ou arrays primitivos ao invés de Streams.


14. Use @Serial para Serialização Segura

O Java 14+ introduziu a anotação @Serial para marcar explicitamente campos e métodos relacionados à serialização, ajudando a evitar erros sutis.

public class MinhaClasse implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    
    @Serial
    private void writeObject(ObjectOutputStream out) throws IOException {
        // Lógica customizada
    }
}

15. Scoped Values (Preview no Java 21+)

Os Scoped Values são uma alternativa moderna e mais segura ao ThreadLocal, especialmente útil com Virtual Threads. Eles permitem compartilhar dados imutáveis dentro de um escopo definido sem vazamento de memória.

private static final ScopedValue<String>; USER = ScopedValue.newInstance();

ScopedValue.where(USER, "João").run(() -> {
    System.out.println(USER.get()); // "João"
});

Resumo Visual: Performance vs. Legibilidade

🔴 Problema ✅ Solução 💎 Benefício 🏷️ Tag
Concatenação de Strings em Loop StringBuilder / StringBuffer Performance O(N) ao invés de O(N²) Performance Crítica
ArrayList.contains() repetido HashSet para lookups O(1) vs O(N) Java Básico
Regex compilada em loop Pattern como constante static final Reduz sobrecarga de compilação Performance Crítica
Sincronização de método inteiro Bloco sincronizado ou AtomicInteger Melhor concorrência Threads
Threads tradicionais para I/O Virtual Threads (Java 21+) Milhares de threads leves Java 21+
instanceof + cast repetidos Pattern Matching em Switch Código mais limpo e seguro Java 21+
Boilerplate de DTOs Record Classes Código conciso e imutável Java 14+
Strings multilinha concatenadas Text Blocks Legibilidade extrema Java 15+

Linha do Tempo: Recursos Modernos do Java

💡 Java 14 (2020) -> Records (Preview), Text Blocks, @Serial, Pattern Matching para instanceof (Preview)
Referência: OpenJDK 14

✅ Java 15 (2020) -> Text Blocks (Estável), Sealed Classes (Preview)
Referência: OpenJDK 15

⚡ Java 17 LTS (2021) -> Sealed Classes (Estável), Pattern Matching para instanceof (Estável)
Referência: OpenJDK 17

🔥 Java 21 LTS (2023) -> Virtual Threads, Structured Concurrency (Preview), Scoped Values (Preview), Pattern Matching em Switch (Estável)
Referência: OpenJDK 21

🚀 Java 25 LTS (2025) -> PEM Encodings of Cryptographic Objects (Preview), Stable Values (Preview), Remove the 32-bit x86 Port, Structured Concurrency (Fifth Preview), Scoped Values, Primitive Types in Patterns, instanceof, and switch (Third Preview), Vector API (Tenth Incubator), JFR CPU-Time Profiling (Experimental), Key Derivation Function API, Module Import Declarations, Compact Source Files and Instance Main Methods, Flexible Constructor Bodies, Ahead-of-Time Command-Line Ergonomics, Ahead-of-Time Method Profiling, JFR Cooperative Sampling, Compact Object Headers, JFR Method Timing & Tracing, Generational Shenandoah
Referência: OpenJDK 25


Dica Pro: Deixe o JIT Trabalhar

🤖 O Compilador JIT é seu amigo!
Ele já faz automaticamente:

  • Method Inlining: Copia métodos pequenos direto no local de chamada
  • Loop Invariant Hoisting: Move código que não muda para fora do loop
  • Dead Code Elimination: Remove código que nunca é executado

Moral da história: Escreva código limpo primeiro, otimize depois se necessário!


🎓 Checklist Final: Código Java Moderno e Eficiente

  • ☑️ Usar StringBuilder em loops com concatenação
  • ☑️ Preferir HashSet para verificações de existência
  • ☑️ Compilar regex uma única vez como constante
  • ☑️ Sincronizar apenas blocos críticos
  • ☑️ Avaliar Virtual Threads para aplicações I/O-bound
  • ☑️ Usar Pattern Matching em switch quando possível
  • ☑️ Criar DTOs com Records
  • ☑️ Usar Text Blocks para strings multilinha
  • ☑️ Considerar Sealed Classes para hierarquias controladas
  • ☑️ Evitar Streams em contextos de alta performance

💡 Lembre-se: IA pode gerar código rápido, mas você garante que seja código inteligente!

← Voltar aos Artigos