Java Avançado¶
Recursos de Java que vão além da sintaxe básica — concorrência, reflection, módulos, serialização, I/O e logging. A base da linguagem está em Java, os conceitos de OO em Orientação a Objetos e os padrões de projeto em Padrões Arquiteturais.
Generics avançados¶
Sem generics, coleções guardam Object e exigem casts manuais — erros só aparecem em
tempo de execução (ClassCastException). Com eles, o compilador valida os tipos.
class Caixa<T> { // classe genérica
private T valor;
void setValor(T valor) { this.valor = valor; }
T getValor() { return valor; }
}
public static <T> T primeiro(List<T> lista) { return lista.get(0); } // método genérico
| Recurso | Sintaxe | Uso |
|---|---|---|
| Tipo limitado (bounded type) | <T extends Number> |
Restringe os tipos aceitos (T tem de ser Number ou subtipo) |
Wildcard ? |
List<?> |
Tipo desconhecido (leitura de qualquer lista) |
| Limite superior | List<? extends Number> |
Aceita Number e subtipos; só leitura (producer) |
| Limite inferior | List<? super Integer> |
Aceita Integer e supertipos; escrita (consumer) |
| Diamante | new ArrayList<>() |
O compilador infere o tipo (Java 7+) |
Definição: PECS
Producer Extends, Consumer Super: use ? extends T quando a estrutura fornece
elementos (você só lê) e ? super T quando ela consome elementos (você escreve).
Definição: Type erasure (apagamento de tipos)
Os tipos genéricos existem só em tempo de compilação: em execução, List<String>
vira List e a informação do tipo desaparece. Por isso não se pode usar primitivos
(List<int> — use Integer) nem criar new T().
enum em detalhe¶
enum Nivel {
BASICO(1, "Básico"), INTERMEDIARIO(2, "Intermediário"), AVANCADO(3, "Avançado");
private final int codigo;
private final String descricao;
private Nivel(int codigo, String descricao) { this.codigo = codigo; this.descricao = descricao; }
public int getCodigo() { return codigo; }
}
Nivel n = Nivel.valueOf("AVANCADO"); // IllegalArgumentException se o nome não existir
for (Nivel x : Nivel.values()) System.out.println(x);
- Compare enums com
==(seguro); funcionam emswitch. ordinal()devolve a posição, mas não use como identificador persistente: a ordem pode mudar quando o enum evoluir — grave um código explícito (comocodigoacima).- Bons usos: estados (pedido PENDENTE/ENVIADO/ENTREGUE), níveis, tipos de usuário, dias da
semana, configurações. Trate entradas externas, pois
valueOfpode lançar exceção.
Datas e horas: cuidados¶
Complementa java.time.
| Classe | Representa |
|---|---|
LocalDate / LocalTime / LocalDateTime |
Data / hora / ambas, sem fuso horário |
Instant |
Instante na linha do tempo em UTC |
ZonedDateTime |
Data e hora com fuso |
Period x Duration |
Período (dias/meses/anos) x duração (horas/minutos/segundos) |
Evite as antigas Date e Calendar; considere fuso horário e horário de verão em
aplicações globais; os objetos são imutáveis (plusDays devolve um novo objeto).
Armazene instantes em UTC e converta para exibir.
Arquivos e I/O¶
Path path = Paths.get("dados.txt");
String texto = Files.readString(path); // UTF-8 por padrão
List<String> linhas = Files.readAllLines(path);
Files.writeString(path, "Execução concluída!\n",
StandardOpenOption.CREATE, StandardOpenOption.APPEND); // CREATE, APPEND, TRUNCATE_EXISTING
try (Stream<String> s = Files.lines(path)) { // lê em fluxo: arquivos grandes
s.filter(l -> l.contains("Java")).forEach(System.out::println);
}
try (BufferedReader br = Files.newBufferedReader(path)) { /* linha a linha */ }
- Operações de arquivo lançam
IOException: trate comtry-catchou declarethrows. Files.linesdevolve umStreamque deve ser fechado (use try-with-resources).- Segurança (path traversal): valide caminhos recebidos do usuário —
base.resolve(entrada).normalize()e verifique questartsWith(base), impedindo../../config.txt.
Lambdas e interfaces funcionais¶
Complementa Expressões lambda e Streams.
| Interface | Método | Função |
|---|---|---|
Predicate<T> |
boolean test(T) |
Condição (filtrar) |
Function<T,R> |
R apply(T) |
Transformar |
Consumer<T> |
void accept(T) |
Executar uma ação |
Supplier<T> |
T get() |
Fornecer um valor |
BiFunction<T,U,R> |
R apply(T,U) |
Transformar dois valores |
Function<String, String> upper = String::toUpperCase; // Classe::método
Consumer<String> printer = System.out::println; // objeto::método
BiFunction<String, Integer, Pessoa> criador = Pessoa::new; // Classe::new
Usos: Comparator, filter/map/forEach de coleções, callbacks e tratamento de
eventos. Use com sabedoria: nomes e expressões continuam precisando ser legíveis.
Streams: operações¶
Um stream é um fluxo de elementos (não uma coleção) e só pode ser consumido uma vez. O pipeline tem uma fonte, operações intermediárias (preguiçosas — só executam quando há operação terminal) e uma operação terminal.
| Operação | Tipo | Efeito |
|---|---|---|
filter(Predicate) |
Intermediária | Mantém só o que satisfaz a condição |
map(Function) |
Intermediária | Transforma cada elemento |
sorted() / distinct() |
Intermediária | Ordena / remove duplicados |
reduce(0, Integer::sum) |
Terminal | Combina tudo em um valor |
collect(...) |
Terminal | Coleta em List, Set, Map (Collectors.toMap) |
Evite efeitos colaterais (não altere estado externo dentro do pipeline). O
parallelStream() usa várias threads, mas exige cuidado com a ordem e com operações não
associativas.
Anotações e Reflection¶
Anotações customizadas¶
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MinhaAnotacao {
String valor() default "";
int prioridade() default 0;
}
| Meta-anotação | Valores |
|---|---|
@Retention (até quando existe) |
SOURCE (só no código-fonte), CLASS (no .class, padrão), RUNTIME (disponível em execução, por reflection) |
@Target (onde pode ser usada) |
TYPE, METHOD, FIELD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, PACKAGE |
Anotações não alteram diretamente o comportamento do programa — fornecem metadados que o compilador, ferramentas e frameworks (Spring, JPA, JUnit, Jackson) leem, em compilação (annotation processing) ou em execução (reflection).
Reflection¶
Definição: Reflection
API que permite inspecionar e manipular classes, métodos, campos e construtores em tempo de execução, mesmo sem conhecê-los em tempo de compilação.
Class<?> c = Pessoa.class; // ou obj.getClass(), Class.forName("pacote.Pessoa")
Method m = c.getMethod("somar", int.class, int.class);
Object r = m.invoke(new Calculadora(), 3, 5); // 8
Field campo = Pessoa.class.getDeclaredField("idade");
campo.setAccessible(true); // acessa membro privado
campo.set(pessoa, 30);
Pessoa p = (Pessoa) c.getConstructor(String.class, int.class).newInstance("Ana", 25);
- Aplicações: Spring (injeção de dependência), Hibernate/JPA (mapeamento), Jackson (serialização JSON), JUnit, plugins e descoberta de anotações.
- Cautelas: é mais lenta que chamadas diretas, quebra o encapsulamento
(
setAccessible) e pode falhar com o sistema de módulos (Java 9+); trateNoSuchMethodException,IllegalAccessExceptioneInvocationTargetException; use só quando realmente necessário.
Optional em detalhe¶
Complementa Optional.
Optional<String> a = Optional.of("Java"); // exige valor não nulo
Optional<String> b = Optional.ofNullable(valor); // aceita null
Optional<String> c = Optional.empty();
b.isPresent(); b.isEmpty(); // isEmpty: Java 11+
b.orElse("padrão"); // sempre avaliado
b.orElseGet(() -> buscarPadrao()); // só se vazio (preferir se for custoso)
b.orElseThrow(() -> new IllegalStateException("Valor ausente"));
Optional<Integer> tam = b.map(String::length); // transforma
Optional<Integer> r = b.flatMap(s -> Optional.of(s.length())); // evita Optional<Optional<T>>
b.filter(s -> s.length() > 3); // mantém só se a condição for verdadeira
Boas práticas: não usar como campo de classe nem parâmetro de método; usar como tipo de
retorno; evitar get() direto; preferir orElseGet a orElse quando o valor padrão for
custoso; lembrar que Optional não é serializável (evite em entidades).
Threads e concorrência¶
Definição: Processo x Thread
Processo: programa em execução, com recursos próprios (memória, arquivos). Thread: linha de execução dentro de um processo; várias threads compartilham os recursos do mesmo processo e executam tarefas ao mesmo tempo.
class MinhaTarefa implements Runnable { // preferível a estender Thread
@Override public void run() { System.out.println("Executando..."); }
}
Thread t = new Thread(new MinhaTarefa());
t.start(); // cria uma nova thread
// t.run(); // ERRADO: executa no thread atual, sem criar thread nova
Runnablepermite herdar de outra classe e se integra a executors.- Estados:
NEW→RUNNABLE→RUNNING→BLOCKED/WAITING→TERMINATED(uma thread finalizada não pode ser reiniciada). Thread.sleep(ms)pausa (lançaInterruptedException);join()espera outra thread terminar;interrupt()solicita a interrupção de forma cooperativa — a thread deve checarisInterrupted()e tratar aInterruptedException.
Sincronização¶
Definição: Race condition
Condição de corrida: várias threads acessam e alteram o mesmo dado e o resultado
depende da ordem de execução (ex.: dois contador++ simultâneos podem gravar 1 em vez
de 2).
| Recurso | Função |
|---|---|
synchronized |
Exclusão mútua: uma thread por vez no método ou bloco (synchronized (objeto) { ... }); o lock é do objeto |
Lock (ReentrantLock) |
Mais flexível: tryLock com timeout, interrompível, fairness; sempre unlock() em finally |
volatile |
Garante visibilidade da variável entre threads; não torna operações compostas atômicas (útil para flags) |
Classes atômicas (AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference) |
Operações atômicas sem bloqueio: contador.incrementAndGet() |
private final AtomicInteger contador = new AtomicInteger(0);
public void inc() { contador.incrementAndGet(); }
Lock lock = new ReentrantLock();
lock.lock();
try { /* seção crítica */ } finally { lock.unlock(); }
Definição: Deadlock
Threads bloqueadas para sempre, cada uma esperando um recurso que a outra segura. Prevenção: adquira os locks sempre na mesma ordem, evite segurá-los por muito tempo e minimize o uso de locks.
ExecutorService e Future¶
Em aplicações reais, prefira pools de threads a criar Thread manualmente.
ExecutorService executor = Executors.newFixedThreadPool(4);
Callable<Integer> tarefa = () -> { Thread.sleep(1000); return 42; }; // devolve resultado
Future<Integer> future = executor.submit(tarefa);
try {
Integer r = future.get(2, TimeUnit.SECONDS); // espera, com timeout
} catch (TimeoutException e) { System.out.println("Tempo esgotado!"); }
executor.shutdown(); // encerra graciosamente
executor.awaitTermination(10, TimeUnit.SECONDS);
// shutdownNow() tenta cancelar as tarefas em andamento
Fábrica (Executors) |
Pool |
|---|---|
newFixedThreadPool(n) |
Tamanho fixo; fila ilimitada |
newCachedThreadPool() |
Cria threads sob demanda |
newSingleThreadExecutor() |
Uma thread; execução em ordem FIFO |
newScheduledThreadPool(n) |
Tarefas com atraso ou periódicas |
Callable é como Runnable, mas retorna resultado e pode lançar exceção verificada.
Future representa o resultado futuro (isDone, get, cancelamento). Sempre finalize o
executor para liberar recursos.
CompletableFuture: fluxos assíncronos não bloqueantes¶
CompletableFuture<String> f = CompletableFuture
.supplyAsync(() -> "id") // runAsync se não devolve valor
.thenCompose(id -> buscarUsuario(id)) // encadeia outra tarefa assíncrona
.thenApply(String::toUpperCase) // transforma o resultado
.exceptionally(ex -> "valor alternativo"); // erro: valor padrão
CompletableFuture<String> c = f1.thenCombine(f2, (a, b) -> a + b); // combina duas independentes
| Método | Quando |
|---|---|
thenApply |
Transforma o resultado (função simples) |
thenCompose |
A próxima etapa também devolve um CompletableFuture (evita aninhamento) |
thenCombine |
Combina o resultado de duas tarefas independentes |
exceptionally |
Trata o erro devolvendo um valor alternativo |
handle |
Trata resultado e exceção (sempre executado) |
Passe um Executor próprio para operações de I/O longas (evita sobrecarregar o
ForkJoinPool comum). Boas práticas gerais de threads: não compartilhe estado sem
proteção, use sincronização só quando necessário, cheque o estado de interrupção, finalize
recursos e prefira executors a threads manuais.
Serialização¶
Definição: Serialização
Converter um objeto em uma sequência de bytes (para gravar em arquivo, enviar pela
rede, guardar em cache) e reconstruí-lo depois (desserialização). Em Java nativo, a
classe implementa a interface marcadora Serializable.
public class Usuario implements Serializable {
private static final long serialVersionUID = 1L;
private String nome;
private transient String senha; // transient: NÃO é serializado
}
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("usuario.bin"))) {
oos.writeObject(usuario);
}
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("usuario.bin"))) {
Usuario u = (Usuario) ois.readObject();
}
serialVersionUID: identifica a versão da classe; defina-o explicitamente (evitaInvalidClassExceptionquando a classe evolui) e mantenha compatibilidade (adicione campos, evite remover).transient: exclui campos sensíveis ou calculados; após desserializar, ficam com o valor padrão (null,0,false).- Riscos: nunca desserialize dados não confiáveis — objetos maliciosos podem executar código (gadget chains); valide e filtre o que entra.
- Alternativa moderna: JSON com Jackson/Gson — legível, interoperável, mais
seguro, formato padrão de APIs. Use DTOs (ou
records) para transferir só os dados necessários.
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(usuario); // serializa
Usuario u = mapper.readValue(json, Usuario.class); // desserializa
JUnit 5 e testes unitários¶
Complementa Qualidade.
- Um teste unitário verifica uma pequena unidade (método ou classe) de forma automática e isolada: detecta erros cedo, facilita a manutenção e aumenta a confiança.
- Padrão AAA: Arrange (prepara o cenário), Act (executa) e Assert (verifica).
@Test
void deveCalcularTotal() {
Pedido pedido = new Pedido(100.0); // Arrange
double total = pedido.calcularTotal(); // Act
assertEquals(100.0, total); // Assert
}
@ParameterizedTest
@ValueSource(ints = {2, 4, 6})
void deveSerPar(int numero) { assertTrue(numero % 2 == 0); }
- Assertions:
assertEquals,assertTrue,assertFalse,assertNull,assertNotNull,assertThrows. Testes parametrizados rodam o mesmo teste com várias entradas (@ValueSource,@CsvSource,@MethodSource,@EnumSource). - Mocks e fakes simulam dependências (Mockito:
@Mock,when(...).thenReturn(...)). - Cobertura (JaCoCo, IntelliJ, SonarQube) mostra quanto do código os testes executam — não substitui a qualidade dos testes; cubra regras de negócio e casos relevantes.
- Cada teste deve ser independente (sem depender da ordem, de banco real ou de recursos externos), rápido e determinístico. Ciclo TDD: vermelho → verde → refatorar.
Qualidade e build¶
| Ferramenta | Função |
|---|---|
Maven (pom.xml) |
Build e dependências; ciclo de vida: validate, compile, test, package, verify, install, deploy |
Gradle (build.gradle) |
Build flexível e incremental; suporta multiprojetos; scripts em Groovy ou Kotlin |
| Dependências | Resolvidas e baixadas automaticamente; escopos (compile, test, runtime); verifique conflitos |
| JAR | Resultado do build; java -jar app.jar; o JAR "fat" inclui as dependências (ex.: Spring Boot) |
| Git | Versionamento; mensagens claras; fluxo de branches |
| CI (GitHub Actions, GitLab CI, Jenkins) | Executa build e testes a cada alteração e gera o artefato |
| Análise estática (SonarQube, Checkstyle, PMD, SpotBugs) | Detecta má práticas e vulnerabilidades |
(CI/CD e pipelines em Spring — CI/CD.)
Memória e GC: não dependa do coletor¶
O garbage collector libera objetos sem referência, mas você não controla quando ele roda. Algumas consequências práticas:
- Não chame
System.gc()esperando liberar memória: é só uma sugestão, a JVM pode ignorá-la (ou ela pode ser desabilitada por parâmetro), e forçá-la em laços causa pausas desnecessárias. - Evite
finalize()(obsoleto desde o Java 9 e marcado para remoção): não há garantia de quando ou se será executado, pode "ressuscitar" o objeto, atrasa a coleta e dificulta o raciocínio. Para liberar recursos (arquivos, conexões, sockets), exponha um métodoclose()e usetry-with-resourcescomAutoCloseable. Em casos raros, usejava.lang.ref.Cleaner. - Vazamentos de memória existem em Java: são referências que o programa mantém sem precisar — coleções
estáticas que só crescem, caches sem limite, listeners nunca removidos,
ThreadLocalnão limpo em thread pools. O GC não coleta o que ainda é "alcançável". Diagnóstico: heap dump e análise de retenção (Ferramentas da JVM). - Referências fracas (
WeakReference,WeakHashMap,SoftReference) não impedem a coleta do objeto; úteis para caches que podem ser descartados sob pressão de memória. - JIT e otimização adaptativa: boa parte do desempenho do Java vem de otimizar o código conforme o uso real (inlining de métodos pequenos, remoção de código morto). Por isso métodos pequenos e simples costumam ser bons para o JIT, e microbenchmarks sem warm-up enganam (use JMH).
ClassLoader e o classloader hell¶
Definição: ClassLoader
ClassLoader é o objeto da JVM que carrega classes para a memória a partir dos bytes do
bytecode (java.lang.ClassLoader). Uma classe é identificada na JVM pelo nome completo + o ClassLoader
que a carregou: a mesma classe carregada por dois ClassLoaders diferentes são, para a JVM, tipos
distintos.
Hierarquia padrão (cada um tem um pai, exceto o primeiro):
| ClassLoader | Carrega | Observação |
|---|---|---|
| Bootstrap | Classes centrais (java.lang.*, etc.) |
Sem pai; impede que alguém substitua String |
| Platform/Extension | Módulos e extensões da plataforma | No Java 8: Extension; no 9+: Platform |
| Application/System | O classpath da aplicação (-cp) |
Pai: o anterior |
Delegação ao pai: antes de tentar carregar uma classe, o ClassLoader pergunta ao pai (modelo parent-first). Isso protege as classes centrais, mas explica problemas clássicos.
O problema (classloader hell): servidores de aplicação rodam várias aplicações na mesma JVM com um ClassLoader por aplicação. Se alguém copia um JAR para um diretório compartilhado (ou para o classpath global), ele passa a ser carregado pelo ClassLoader pai e vence a versão que cada aplicação trouxe. Sintomas:
NoSuchMethodError/NoSuchFieldError/AbstractMethodError: a aplicação nova chama um método que só existe na versão mais nova, mas a JVM carregou a antiga.ClassCastException"X cannot be cast to X": duas cópias da mesma classe, de ClassLoaders diferentes.LinkageError,NoClassDefFoundErrore comportamento errático conforme a ordem em que classes foram carregadas.- Vazamento de Metaspace em redeploys a quente: classes e ClassLoaders antigos não são liberados porque
algo ainda os referencia (
ThreadLocal, threads, drivers JDBC).
Como mitigar:
- Cada aplicação traz suas dependências (
WEB-INF/lib), e nada vai para diretórios compartilhados do servidor. Muitos contêineres Web usam o modelo invertido (parent-last) para a aplicação, justamente para que a versão que ela empacotou prevaleça (conforme a especificação de servlets). - Entender a árvore de dependências (
mvn dependency:tree,gradle dependencies) e excluir versões conflitantes; usar dependency management (BOM) para alinhar versões (Governança de dependências). - Shading/relocation (renomear pacotes de uma biblioteca dentro do JAR final) quando duas versões precisam coexistir.
- Módulos (JPMS) e imagens fat jar ou contêineres (uma aplicação por JVM) eliminam grande parte do problema (JPMS).
- Diagnóstico:
-verbose:class(de onde cada classe foi carregada),jcmd VM.classloader_stats.
Pode-se também criar ClassLoaders próprios (URLClassLoader) para plugins e hot swap: o segundo
argumento do construtor decide o pai (null = Bootstrap), o que muda se classes comuns são vistas ou não.
Módulos Java (JPMS)¶
Definição: Módulo Java
Unidade de organização (Java 9+) que agrupa pacotes e declara, no arquivo
module-info.java (na raiz de src/main/java), do que depende e o que
expõe. Fortalece o encapsulamento entre grandes partes da aplicação.
module com.exemplo.app {
requires java.logging; // depende de outro módulo
requires transitive java.sql; // quem usa este módulo também lê java.sql
exports com.exemplo.api; // só estes pacotes ficam visíveis a outros módulos
opens com.exemplo.modelo to spring.core; // abre acesso por reflection (Spring, JPA)
}
| Diretiva | Efeito |
|---|---|
requires |
Declara a dependência de outro módulo (JDK ou de terceiros) |
requires transitive |
A dependência também é "herdada" por quem usa este módulo |
exports |
Torna públicos os pacotes para outros módulos (pacote não exportado fica encapsulado) |
opens |
Permite acesso profundo por reflection (diferente de exports, que expõe a API) |
Executa-se com java --module-path mods -m com.exemplo.app/.... A ferramenta jlink
cria uma imagem de runtime personalizada só com os módulos necessários (menor e mais
segura). Módulos nomeados têm module-info.java; o código no classpath forma o módulo
"sem nome".
Configuração e ambientes¶
| Mecanismo | Uso |
|---|---|
.properties |
chave=valor, comentários com #; carregado do classpath |
| YAML | Hierárquico e legível (muito usado no Spring Boot) |
| Variáveis de ambiente | System.getenv("APP_NOME") — independentes do código; úteis em contêineres |
| Propriedades da JVM | -Dapp.nome=MeuProjeto lidas com System.getProperty("app.nome", "padrão") |
| Perfis | dev, test, prod: configuração separada por ambiente |
| Segredos | Senhas e chaves em variáveis de ambiente ou cofre — nunca no Git |
Valide as configurações na inicialização (falhar cedo, com mensagem clara, se uma variável obrigatória estiver ausente). Detalhes em Spring — Configuração.
Logging¶
O SLF4J é a fachada de logging (API simples, independente da implementação); Logback é a implementação padrão, usada pelo Spring Boot.
private static final Logger log = LoggerFactory.getLogger(PedidoService.class);
log.info("Processando pedido {} para o usuário {}", pedidoId, usuario); // placeholders {}
log.error("Falha ao processar o pedido {}", pedidoId, ex);
- Níveis:
TRACE,DEBUG,INFO,WARN,ERROR; em produção, eviteTRACE/DEBUG. - Placeholders
{}evitam concatenação desnecessária de strings. - MDC (Mapped Diagnostic Context): adiciona contexto (ex.:
correlationId) a todos os logs da requisição/thread — rastreia fluxos em sistemas distribuídos; remova o contexto ao final. - Rotação de logs: por tamanho/data, com compressão e retenção máxima, para não encher o disco.
- Boas práticas: registre eventos relevantes, use o nível correto, nunca registre dados sensíveis (senhas, tokens), inclua contexto e mantenha o formato consistente. Observabilidade completa em SRE.
HTTP e APIs REST em Java¶
Teoria de HTTP e REST em Backend. O
java.net.http.HttpClient (Java 11+) é o cliente moderno da biblioteca padrão.
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.exemplo.com/dados"))
.header("Accept", "application/json")
.timeout(Duration.ofSeconds(30))
.GET() // .POST(BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
int status = response.statusCode(); // 200, 201, 404, 500...
- Assíncrono:
client.sendAsync(...)devolve umCompletableFuture(não bloqueia). - Tratamento de erros:
IOException,InterruptedExceptione códigos de status inesperados; defina timeouts de conexão e de leitura. - O
HttpClienté imutável e reutilizável. - JSON:
ObjectMapper(Jackson) converte objetos ↔ JSON;records são ótimos DTOs; use Bean Validation para validar os dados recebidos e@JsonIncludepara campos opcionais.
Arquitetura de aplicações Java¶
Princípios de organização (detalhes em Padrões Arquiteturais e Boas Práticas):
| Tema | Resumo |
|---|---|
| Camadas | controller (apresentação) → service (regras de negócio) → repository (acesso a dados) → model (entidades/DTOs) |
| MVC | Model (dados e regras), View (apresentação), Controller (recebe requisições e coordena) |
| Hexagonal | Domínio isolado, com portas (interfaces) e adaptadores externos |
| SOLID | Cinco princípios para código manutenível |
| DTO | Objeto de transferência entre camadas, evitando expor entidades |
| Injeção de dependência | O contêiner (Spring) fornece as dependências, reduzindo acoplamento; prefira injeção por construtor |
| Coesão e acoplamento | Alta coesão (uma responsabilidade por módulo) e baixo acoplamento (interfaces em vez de implementações) |
Padrões de projeto mais comuns¶
| Padrão | Propósito |
|---|---|
| Singleton | Uma única instância com ponto global de acesso (cuidado com estado global) |
| Factory | Centraliza a criação de objetos, desacoplando o cliente do tipo concreto |
| Builder | Constrói objetos complexos passo a passo, evitando construtores telescópicos |
| Strategy | Família de algoritmos intercambiáveis em tempo de execução |
| Observer | Notifica automaticamente os observadores quando o estado muda |
| Adapter | Converte uma interface em outra esperada pelo cliente |
Aplique um padrão somente quando houver o problema que ele resolve: evite engenharia excessiva e prefira a simplicidade. Cada padrão é detalhado, com diagramas e exemplos, em Padrões Arquiteturais.
JVM e desempenho¶
Definição: Heap, stack e Garbage Collector
Heap: memória principal onde ficam os objetos e arrays, dividida em gerações
(young e old); tamanho configurável com -Xms (inicial) e -Xmx (máximo).
Stack: pilha de cada thread, com um quadro por chamada de método (variáveis
locais e parâmetros); tamanho com -Xss; recursão profunda causa StackOverflowError.
Garbage Collector (GC): libera automaticamente objetos que não têm mais referências.
java -Xms256m -Xmx2g -Xss1m -jar minhaapp.jar
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar minhaapp.jar
java -Xlog:gc*:file=gc.log:time,level,tags -jar minhaapp.jar # log detalhado de GC
| Tema | Resumo |
|---|---|
| G1 (Garbage First) | Coletor padrão desde o Java 9: coleta incremental e paralela, divide o heap em regiões e prioriza as com mais lixo; pausas previsíveis (ideal para baixa latência) |
| JIT (Just-In-Time) | Compila o bytecode em código nativo durante a execução, otimizando os métodos mais usados (hot spots); melhora com o tempo ("aquecimento") |
| Latência x throughput | Latência: tempo de uma operação; throughput: operações por unidade de tempo. Há trade-off entre eles — escolha pelo objetivo |
| Pools | Reutilizam recursos caros (ExecutorService, HikariCP, buffers); ajuste o tamanho conforme a carga |
| Boas práticas | Usar versão LTS recente, ajustar -Xms/-Xmx, evitar alocação excessiva de objetos, monitorar GC e medir antes de otimizar (teste com carga realista) |
Profiling: use o Java Flight Recorder (JFR) (-XX:StartFlightRecording=duration=60s,
filename=perfil.jfr), VisualVM ou JProfiler para achar gargalos de CPU, memória e
threads.
Ferramentas da JVM¶
| Ferramenta | Função |
|---|---|
jps |
Lista JVMs em execução (PID e classe principal) |
jstack <pid> |
Thread dump: mostra o estado das threads e detecta deadlocks |
jmap -dump:format=b,file=heap.hprof <pid> |
Heap dump para investigar vazamentos (analisar no VisualVM ou Eclipse MAT) |
jcmd <pid> ... |
Ferramenta versátil: VM.version, Thread.print, GC.heap_dump |
jconsole / VisualVM |
Monitoramento gráfico (CPU, memória, threads, GC; JMX) |
| JFR | Coleta contínua com baixo overhead; análise no JDK Mission Control |
Roteiro de diagnóstico: identificar o PID (jps) → verificar CPU/memória → thread dump
(jstack) para travamentos → heap dump (jmap) para vazamentos → analisar.
GraalVM e Native Image¶
Definição: GraalVM e Native Image
GraalVM é uma JVM de alto desempenho com a ferramenta native-image, que
compila a aplicação antecipadamente (AOT) em um executável nativo que dispensa
a JVM em produção. Resultado: inicialização em milissegundos e menor uso de memória
(ideal para serverless e contêineres).
native-image -jar minha-app.jar minha-app # gera o executável
./mvnw -Pnative native:compile # Spring Boot
- Limitações: análise estática do código — reflection, proxies e carregamento dinâmico exigem configuração (arquivos JSON ou hints); build mais lento; nem toda biblioteca é compatível. Frameworks (Spring, Quarkus) geram parte da configuração.
- Compare: JVM tradicional ~2–5 s de startup; nativo ~dezenas de ms.
Java moderno (17 a 21)¶
Linha do tempo (versões LTS): Java 8 (2014: lambdas, streams), 11 (2018), 17 (2021), 21 (2023). Principais recursos recentes:
record avançado¶
public record Cliente(Long id, String nome, String email) {
public Cliente { // construtor compacto: validação
Objects.requireNonNull(nome, "nome obrigatório");
email = email.trim().toLowerCase(); // normaliza o parâmetro
}
public String dominio() { return email.substring(email.indexOf('@') + 1); }
public static Cliente vazio() { return new Cliente(0L, "", "x@x"); }
}
Pode implementar interfaces, ter métodos e static; é imutável. Limites: não
estende outras classes, campos são final, não serve como entidade JPA (precisa de
construtor sem argumentos e campos mutáveis). Ótimo para DTOs.
sealed (classes seladas)¶
public sealed interface Forma permits Circulo, Retangulo, Quadrado {}
public record Circulo(double raio) implements Forma {}
public record Retangulo(double l, double a) implements Forma {}
public final class Quadrado implements Forma { /* ... */ }
sealed restringe quais tipos podem estender/implementar; permits os lista; as
subclasses devem ser final, sealed ou non-sealed (abertas). Modela domínios
fechados (tipos de pagamento, estados de pedido) e permite ao compilador verificar a
exaustividade de um switch.
Pattern matching¶
if (obj instanceof String s && s.length() > 3) { System.out.println(s.toUpperCase()); }
String descricao = switch (obj) {
case Integer i when i > 10 -> "Inteiro grande: " + i; // guarda (when)
case Integer i -> "Inteiro: " + i;
case String s -> "Texto: " + s;
case null -> "Valor nulo"; // trata null
default -> "Outro";
};
double area(Forma f) { // sem default: sealed é exaustivo
return switch (f) {
case Circulo c -> Math.PI * c.raio() * c.raio();
case Retangulo r -> r.l() * r.a();
case Quadrado q -> q.lado() * q.lado();
};
}
instanceof com padrão (Java 16) elimina o cast; switch com padrões (Java 21)
substitui cadeias de if/instanceof. Cuidado com a ordem dos case (do mais
específico ao mais geral) e mantenha as guardas simples.
Threads virtuais (Project Loom, Java 21)¶
Definição: Thread virtual
Thread leve, gerenciada pela JVM (não pelo SO), com custo de memória muito baixo: é possível ter milhares ou milhões. Quando uma thread virtual bloqueia (I/O, banco), a JVM a desmonta da thread de plataforma, liberando-a para outras tarefas.
Thread.startVirtualThread(() -> System.out.println("virtual"));
Thread t = Thread.ofVirtual().name("v-1").start(() -> processar());
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) executor.submit(() -> tratarRequisicao());
}
Mantém o modelo imperativo (sem programação reativa) e é compatível com o código existente. Ideal para servidores com muita concorrência e I/O bloqueante. Não use pools de threads virtuais (crie uma por tarefa). Não acelera tarefas de CPU intensivo.
Concorrência estruturada (preview no Java 21)¶
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Subtask<Usuario> u = scope.fork(() -> buscarUsuario(id));
Subtask<Pedidos> p = scope.fork(() -> buscarPedidos(id));
scope.join().throwIfFailed(); // espera e propaga a primeira falha
return new Perfil(u.get(), p.get());
}
Trata um grupo de subtarefas como uma unidade com ciclo de vida definido: se uma falha
(ShutdownOnFailure) ou termina (ShutdownOnSuccess), as demais são canceladas; evita
vazamento de threads. Recurso em evolução — verifique a versão.
Programação reativa¶
Definição: Programação reativa
Paradigma baseado em fluxos de dados assíncronos e não bloqueantes, com backpressure (o consumidor controla o ritmo do produtor). Baseia-se em Publisher, Subscriber, Subscription e Processor (Reactive Streams).
| Tipo (Reactor) | Emite |
|---|---|
Mono<T> |
0 ou 1 elemento (resposta única) |
Flux<T> |
0 a N elementos (sequências, eventos) |
Flux.range(1, 5).filter(n -> n % 2 == 0).map(n -> n * 10)
.subscribe(System.out::println); // 20, 40
Mono.just("Ana").map(String::toUpperCase).onErrorReturn("padrão");
- Operadores:
map,filter,flatMap,take,zip,merge,retry,timeout,onErrorResume... Nada acontece até alguém fazersubscribe. - Spring WebFlux: stack reativa do Spring sobre Netty (event loop, poucas
threads):
@RestControllercomMono/FluxouRouterFunction+Handler; clienteWebClient(substitui oRestTemplate). - Bloqueante x reativo: Servlet/Spring MVC usa uma thread por requisição (simples); reativo usa poucas threads para muitas conexões (mais escalável, mais complexo). Hoje, threads virtuais são uma alternativa que mantém o código imperativo.
Collections avançadas¶
| Estrutura | Complexidade típica | Observação |
|---|---|---|
ArrayList |
acesso O(1); inserção/remoção no meio O(n) | Melhor padrão para listas |
LinkedList |
acesso O(n); inserção nas pontas O(1) | Raramente melhor que ArrayDeque |
HashMap / HashSet |
busca/inserção O(1) médio | Sem ordem |
TreeMap / TreeSet |
O(log n) | Mantêm ordem das chaves |
ArrayDeque |
pontas O(1) | Fila e pilha |
ConcurrentHashMap:Mapthread-safe de alto desempenho (bloqueio granular):putIfAbsent,compute,mergeatômicos.- Coleções imutáveis:
List.of(...),Set.of(...),Map.of(...)(lançamUnsupportedOperationExceptionao alterar);Collections.unmodifiableListé só uma visão. Comparator:Comparator.comparing(Produto::getPreco).thenComparing(Produto::getNome).
Streams avançados¶
| Operação | Uso |
|---|---|
flatMap |
"Achata" coleções internas em um só fluxo (pessoas.stream().flatMap(p -> p.getEmails().stream())) |
Collectors.groupingBy(chave) |
Agrupa em Map<K, List<V>>; combine com counting(), summingDouble(), mapping() |
Collectors.partitioningBy(pred) |
Divide em duas listas (true/false) |
Collectors.reducing, toMap, joining |
Reduções e agrupamentos prontos |
parallelStream()¶
Processa em várias threads via ForkJoinPool comum (número de núcleos − 1). Use
só para coleções grandes e operações independentes, sem estado compartilhado e custosas
(CPU); há overhead de divisão — meça (JMH). Não use para I/O bloqueante nem quando a
ordem importa (forEachOrdered). Use coletores concorrentes (groupingByConcurrent) com
cuidado.
NIO.2: arquivos e diretórios¶
Complementa Arquivos e I/O.
Path dir = Path.of("docs");
Files.createDirectories(dir);
try (Stream<Path> paths = Files.list(dir)) { paths.forEach(System.out::println); }
Files.walk(dir) // percorre recursivamente
Files.copy(origem, destino, StandardCopyOption.REPLACE_EXISTING);
Files.move(origem, destino, StandardCopyOption.ATOMIC_MOVE);
Pathé imutável e portável (resolve,relativize,normalize,getFileName).WatchServicemonitora criação, alteração e remoção de arquivos em um diretório.- Permissões POSIX:
Files.setPosixFilePermissions(nem todo sistema suporta).
Redes e sockets¶
try (Socket socket = new Socket("exemplo.com", 8080);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
out.println("Olá servidor!");
}
try (ServerSocket server = new ServerSocket(8080)) {
while (true) { Socket cliente = server.accept(); /* uma thread por cliente */ }
}
- TCP (
Socket/ServerSocket): confiável, orientado a conexão, ordem garantida. UDP (DatagramSocket): mais rápido, sem garantia de entrega. - Defina timeouts (
setSoTimeout,connectTimeout) e feche os recursos (try-with-resources). Portas: bem conhecidas (0–1023), registradas (1024–49151), dinâmicas (49152–65535). Para HTTP use oHttpClient(Java Avançado). Modelo OSI em Redes.
Criptografia em Java¶
MessageDigest md = MessageDigest.getInstance("SHA-256"); // hash (não reversível)
byte[] hash = md.digest("Java".getBytes(StandardCharsets.UTF_8));
Mac mac = Mac.getInstance("HmacSHA256"); // autenticidade com chave
mac.init(new SecretKeySpec(chave, "HmacSHA256"));
KeyGenerator kg = KeyGenerator.getInstance("AES"); kg.init(256); // simétrica
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(2048); // assimétrica
| Conceito | Resumo |
|---|---|
Hash (MessageDigest, SHA-256) |
Resumo de tamanho fixo; verifica integridade; não serve sozinho para senhas (use BCrypt/Argon2) |
| HMAC | Hash com chave secreta: integridade e autenticidade (usado em JWT) |
| AES (simétrica) | Mesma chave cifra e decifra; rápida; use modos GCM (com autenticação) e IV aleatório |
| RSA (assimétrica) | Par de chaves (pública/privada); cifra, assinatura digital e troca de chaves; mais lenta |
KeyStore |
Armazena chaves e certificados com segurança (JKS/PKCS12) |
| TLS | Protocolo de comunicação segura (HTTPS); SSLContext e certificados X.509 |
Boas práticas: algoritmos modernos (AES-256-GCM, SHA-256); nunca invente criptografia; chaves fora do código; evite MD5 e SHA-1; rotacione chaves. Teoria em Segurança.
Internacionalização (i18n)¶
Locale pt = new Locale("pt", "BR");
ResourceBundle msgs = ResourceBundle.getBundle("messages", pt); // messages_pt_BR.properties
String titulo = msgs.getString("app.titulo");
String texto = MessageFormat.format(msgs.getString("pedido.confirmado"), 123);
NumberFormat moeda = NumberFormat.getCurrencyInstance(pt); // R$ 1.234,56
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).withLocale(pt);
Separe texto do código em arquivos de recurso, formate números, datas e moedas por
locale, trate fusos horários (ZoneId) e plural (ChoiceFormat/MessageFormat).
Teste vários idiomas. No Spring: MessageSource.
Jakarta EE (visão geral)¶
Definição: Jakarta EE
Conjunto de especificações corporativas do Java (sucessor do Java EE, pacotes
jakarta.*), implementadas por servidores como WildFly, Payara e GlassFish.
| Especificação | Para quê |
|---|---|
| Servlet | Base web: requisições HTTP, sessões, filtros |
| CDI | Injeção de dependências e contextos (@Inject, @ApplicationScoped) |
| JAX-RS | APIs REST (@Path, @GET, @Produces; filtros, interceptors, ExceptionMapper, negociação de conteúdo) |
| JPA | Persistência ORM (Acesso a Dados) |
| Bean Validation | Validação por anotações |
| EJB | Componentes de negócio com transações e segurança (@Stateless) |
| JMS / Mail / Batch / WebSocket / Concurrency | Mensageria, e-mail, processamento em lote, tempo real, concorrência gerenciada |
Empacota-se em WAR/EAR e implanta-se no servidor. O Spring cobre os mesmos problemas
(Spring); a migração
javax.* → jakarta.* ocorreu no Jakarta EE 9 / Spring Boot 3.
Migração de versão do JDK¶
Evolução: Java 8 → 11 → 17 → 21. Passos:
- Inventário de dependências e análise com
jdeps(jdeps --jdk-internals) ejdeprscan(APIs removidas ou depreciadas). - Corrigir o que foi removido: Java EE (
javax.*→jakarta.*),javax.xml.bind,SecurityManager, finalizadores; encapsulamento de módulos (--add-openscomo paliativo). - Atualizar frameworks e bibliotecas (ex.: Spring Boot 2.x → 3.x exige Java 17).
- Testar (unidade, regressão, desempenho) e comparar o GC/JVM flags.
- Rollout gradual (canary/blue-green) com plano de rollback e monitoramento.
Evoluindo sistemas legados¶
- Diagnóstico: mapear módulos, dependências e riscos; métricas de complexidade (SonarQube) e cobertura.
- Testes de caracterização: capturam o comportamento atual antes de refatorar (protegem contra regressões).
- Strangler Fig: criar o novo sistema ao redor do antigo, roteando aos poucos as funcionalidades (feature toggles) até poder desligar o legado.
- Camada anticorrupção (ACL) e APIs para isolar o legado; modularização (Maven multi-módulo) para reduzir acoplamento; migração incremental por funcionalidade com métricas e feedback (Padrões Arquiteturais).
Trilha de domínio: do básico ao profissional¶
Um roteiro para checar se você domina o ecossistema Java, indo do mais fundamental ao mais avançado (cada item aponta para onde o assunto está nesta base):
- Linguagem: sintaxe, tipos, controle de fluxo, strings, exceções, coleções, lambdas e Streams, módulos — Java e este guia.
- Orientação a objetos: classes, herança, polimorfismo, interfaces, composição, SOLID e padrões de projeto — OO, Boas práticas e Padrões.
- APIs do JDK: data/hora, arquivos (NIO.2), concorrência,
HttpClient, serialização, criptografia, i18n. - Frameworks: Spring Boot (injeção de dependências, REST, JPA, segurança, testes) — Spring.
- Dados: JDBC, JPA/Hibernate, SQL, NoSQL, migrações — Acesso a dados com Java.
- Nuvem e operação: contêineres, CI/CD, Kubernetes, observabilidade, SRE — Docker e SRE.
- Segurança e qualidade: OWASP, autenticação/autorização, testes em todos os níveis, análise estática, LGPD.
- Projetos e carreira: construir um projeto de portfólio completo, ler código de outros, contribuir com código aberto e escolher uma especialização (nuvem, mensageria, desempenho, arquitetura).