Novidades do Java: records, sealed classes e virtual threads
O Java, uma das linguagens mais maduras do ecossistema de desenvolvimento, passou por uma transformação significativa nos últimos anos. Três features se destacam por mudar drasticamente a forma como escrevemos código: records, sealed classes e virtual threads. Vamos explorar cada uma delas em detalhes, com exemplos práticos que mostram como essas novidades podem simplificar seu dia a dia.
1. Records: Classes de Dados Imutáveis e Concisas
Records são uma forma concisa de declarar classes que são simples portadoras de dados. Antes dos records, criar uma classe imutável exigia escrever manualmente construtor, getters, equals(), hashCode() e toString().
1.1. Eliminando Boilerplate
Antes dos records:
public class Pessoa {
private final String nome;
private final int idade;
public Pessoa(String nome, int idade) {
this.nome = nome;
this.idade = idade;
}
public String getNome() { return nome; }
public int getIdade() { return idade; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Pessoa)) return false;
Pessoa pessoa = (Pessoa) o;
return idade == pessoa.idade && Objects.equals(nome, pessoa.nome);
}
@Override
public int hashCode() {
return Objects.hash(nome, idade);
}
@Override
public String toString() {
return "Pessoa{nome='" + nome + "', idade=" + idade + "}";
}
}
Com records (Java 16+):
public record Pessoa(String nome, int idade) {}
Um record automaticamente gera:
- Construtor com todos os parâmetros
- Métodos de acesso (sem "get" prefixo, apenas nome() e idade())
- equals() e hashCode() baseados em todos os campos
- toString() legível
1.2. Limitações e Regras
Records não podem estender outras classes (já estendem implicitamente java.lang.Record), mas podem implementar interfaces. Campos são implicitamente final. Você pode adicionar construtores compactos para validação:
public record Email(String endereco) {
public Email {
if (!endereco.contains("@")) {
throw new IllegalArgumentException("Email inválido");
}
}
}
1.3. Casos de Uso
Records são ideais para DTOs, value objects e resultados de operações:
// Usando com Optional e Streams
public record ResultadoBusca(String termo, int totalResultados) {}
List<ResultadoBusca> resultados = termos.stream()
.map(termo -> new ResultadoBusca(termo, buscar(termo)))
.collect(Collectors.toList());
2. Sealed Classes: Hierarquias Controladas e Exaustivas
Sealed classes permitem restringir quais classes podem estender uma classe ou interface, criando hierarquias fechadas e previsíveis.
2.1. Conceito
Com sealed classes, você define explicitamente todas as subclasses permitidas. Isso é poderoso para modelar domínios onde o conjunto de variações é conhecido e limitado.
2.2. Sintaxe e Exemplos Práticos
public sealed class Forma permits Circulo, Retangulo, Triangulo {
public abstract double area();
}
public final class Circulo extends Forma {
private final double raio;
public Circulo(double raio) { this.raio = raio; }
@Override
public double area() { return Math.PI * raio * raio; }
}
public final class Retangulo extends Forma {
private final double largura;
private final double altura;
public Retangulo(double largura, double altura) {
this.largura = largura;
this.altura = altura;
}
@Override
public double area() { return largura * altura; }
}
// Classe non-sealed permite extensão ilimitada
public non-sealed class Triangulo extends Forma {
private final double base;
private final double altura;
public Triangulo(double base, double altura) {
this.base = base;
this.altura = altura;
}
@Override
public double area() { return (base * altura) / 2; }
}
2.3. Benefícios para Pattern Matching
Sealed classes preparam o terreno para pattern matching exaustivo. O compilador pode verificar se todos os casos foram tratados:
public String descreverForma(Forma forma) {
return switch (forma) {
case Circulo c -> "Círculo com raio " + c.raio();
case Retangulo r -> "Retângulo " + r.largura() + "x" + r.altura();
case Triangulo t -> "Triângulo base " + t.base() + " altura " + t.altura();
// Compilador sabe que não precisa de default
};
}
3. Virtual Threads: Concorrência Leve e Escalável
Virtual threads (Project Loom) revolucionam a concorrência no Java, permitindo criar milhões de threads com custo mínimo.
3.1. O Problema das Threads Tradicionais
Threads tradicionais (plataforma threads) são caras: cada uma consome cerca de 1MB de memória de pilha e o escalonamento pelo sistema operacional é custoso. Virtual threads são gerenciadas pela JVM e consomem apenas alguns kilobytes.
3.2. Criando e Gerenciando Virtual Threads
// Criando uma virtual thread diretamente
Thread vThread = Thread.ofVirtual()
.name("minha-virtual-thread")
.start(() -> {
System.out.println("Executando em: " + Thread.currentThread());
});
// Usando ExecutorService com virtual threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
final int taskId = i;
futures.add(executor.submit(() -> {
Thread.sleep(100); // Simula I/O
return "Tarefa " + taskId + " concluída";
}));
}
for (Future<String> future : futures) {
System.out.println(future.get());
}
}
3.3. Limitações e Boas Práticas
Virtual threads têm limitações importantes:
- Pinned threads: operações synchronized e código nativo podem "prender" a thread virtual a uma thread de plataforma
- Pooling desnecessário: não crie pools de virtual threads; crie-as sob demanda
- Não use para CPU-bound: virtual threads brilham em operações I/O, não em processamento intensivo
4. Impacto no Dia a Dia do Desenvolvedor Java
4.1. Redução de Código Boilerplate
Records eliminam dezenas de linhas de código repetitivo. Sealed classes tornam hierarquias mais seguras e explícitas.
4.2. Simplificação de Servidores Web
Com virtual threads, servidores web podem tratar cada requisição em uma thread dedicada sem se preocupar com escalabilidade:
// Servidor HTTP simples com virtual threads
try (var server = Executors.newVirtualThreadPerTaskExecutor()) {
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
Socket clientSocket = serverSocket.accept();
server.submit(() -> handleClient(clientSocket));
}
}
4.3. Exemplo Combinado: Sistema de Pedidos
public sealed interface StatusPedido permits Pendente, Processando, Enviado, Entregue {}
public record Pendente() implements StatusPedido {}
public record Processando() implements StatusPedido {}
public record Enviado(String codigoRastreio) implements StatusPedido {}
public record Entregue() implements StatusPedido {}
public record Pedido(int id, String cliente, List<Item> itens, StatusPedido status) {}
// Processamento com virtual threads
public class ProcessadorPedidos {
public void processar(List<Pedido> pedidos) {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
pedidos.forEach(pedido -> executor.submit(() -> {
var novoStatus = switch (pedido.status()) {
case Pendente p -> new Processando();
case Processando p -> new Enviado(gerarCodigoRastreio());
case Enviado e -> verificarEntrega(e) ? new Entregue() : e;
case Entregue e -> e;
};
atualizarPedido(new Pedido(
pedido.id(), pedido.cliente(), pedido.itens(), novoStatus
));
}));
}
}
}
5. Compatibilidade e Migração para Versões Recentes
5.1. Versões de Introdução
| Feature | Preview | Stable |
|---|---|---|
| Records | Java 14 | Java 16 |
| Sealed Classes | Java 15 | Java 17 |
| Virtual Threads | Java 19 | Java 21 |
5.2. Habilitando Preview Features
Para usar features em preview no compilador e runtime:
# Compilação
javac --enable-preview --release 21 MeuPrograma.java
# Execução
java --enable-preview MeuPrograma
5.3. Estratégias de Adoção
- Records: substitua gradualmente DTOs e value objects
- Sealed Classes: use em domínios fechados como estados de máquina, tipos de eventos
- Virtual Threads: comece em novos serviços I/O-bound, refatore código legado com cuidado
6. Comparação com Outras Linguagens
6.1. Records vs. Data Classes
Kotlin tem data class, C# tem record struct, TypeScript tem tuplas tipadas. Records do Java são mais restritos (imutáveis, sem herança), mas mais integrados ao ecossistema.
6.2. Sealed Classes
Kotlin tem sealed class similar. TypeScript tem union types que são mais flexíveis mas sem verificação exaustiva do compilador.
6.3. Virtual Threads
Go usa goroutines (mais leves, canais nativos). Rust/Kotlin usam async/await (baseado em Futures). Virtual threads são mais próximos de goroutines, mas integrados ao ecossistema Java existente.
Referências
- JEP 395: Records (Official Java Documentation) — Especificação oficial dos records, incluindo detalhes de implementação e exemplos
- JEP 409: Sealed Classes (Official Java Documentation) — Documentação completa sobre sealed classes, sintaxe e casos de uso
- JEP 444: Virtual Threads (Official Java Documentation) — Especificação oficial das virtual threads, com detalhes técnicos e limitações
- Java Records: A Comprehensive Guide (Baeldung) — Tutorial prático sobre records com exemplos de código e boas práticas
- Virtual Threads in Java 21 (InfoQ) — Artigo técnico aprofundado sobre virtual threads, comparações com modelos concorrentes tradicionais
- Sealed Classes in Java 17 (Oracle Dev Blog) — Artigo do Oracle Java Magazine explorando sealed classes com exemplos de domínios fechados
- Project Loom: Virtual Threads Deep Dive (YouTube - Devoxx) — Apresentação técnica detalhada sobre o Project Loom e virtual threads