Java 26: o que são Lazy Constants e por que elas aposentam o double-checked locking (JEP 526)
Source: Dev.to
O que é o JEP 526 do Java 26?
O JEP 526 introduz a classe java.lang.LazyConstant: um contêiner que guarda um valor imutável, inicializado no máximo uma vez.
| Versão | Nome da feature | Observação |
|---|---|---|
| Java 25 | Stable Values (JEP 502) | API mais verbosa, ainda em preview |
| Java 26 | Lazy Constants (JEP 526) | API simplificada, segunda preview |
O problema que ela resolve é antigo:
finalgarante imutabilidade, mas exige inicialização imediata.- Campos não‑
finalpermitem inicialização tardia, mas perdem as garantias de concorrência.
LazyConstant combina os dois mundos: você inicializa quando quiser, mas apenas uma vez, com a mesma segurança de um final.
Por que o double‑checked locking existe e qual é o problema?
Todo desenvolvedor Java já escreveu algo assim:
public class MetricRegistry {
private static volatile MetricRegistry instance;
public static MetricRegistry getInstance() {
if (instance == null) {
synchronized (MetricRegistry.class) {
if (instance == null) {
instance = new MetricRegistry();
}
}
}
return instance;
}
}Funciona, mas exige:
volatile- Dois testes de
null - Um bloco
synchronized
Quem lê o código pela primeira vez costuma ficar confuso. A alternativa com classe interna estática é mais segura, mas ainda é um work‑around. Nenhuma dessas opções é uma abstração de primeira classe para o problema real: “inicialize isso uma vez, quando precisar, de forma segura”.
Como o LazyConstant funciona na prática?
Imagine um serviço de pagamento com um cliente HTTP caro de criar, que só faz sentido existir se a rota for chamada:
public class PaymentService {
private final LazyConstant httpClient =
LazyConstant.of(() -> HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build());
public String charge(String orderId, BigDecimal amount) {
HttpClient client = httpClient.get(); // primeira chamada cria o cliente
// usa o client para chamar o gateway
…
}
}- O
HttpClienté criado na primeira chamada ahttpClient.get(). - Nas chamadas subsequentes a
get(), a JVM devolve o mesmo valor (pode até aplicar constant‑folding). - Thread‑safe por construção, sem
synchronizednemvolatile.
O que mudou do Java 25 para o Java 26 no JEP 526?
- Java 25 – API com métodos de baixo nível (
setOrThrow,trySet,orElseSet), transformando o recurso em um primitivo de sincronização. - Java 26 – Mantém apenas
LazyConstant.of(supplier). Você fornece a função de inicialização e a JVM cuida do resto.
Factories “lazy” para coleções
As factory methods para listas e mapas lazy mudaram de lugar, saindo de StableValue para List e Map diretamente:
// Lista lazy com 10 slots, cada um inicializado de forma independente
List processors = List.ofLazy(10, _ -> new OrderProcessor());
// Mapa lazy com chaves definidas, valores inicializados sob demanda
Map limiters = Map.ofLazy(
Set.of("payments", "refunds", "reports"),
key -> RateLimiter.create(requestsPerSecond(key))
);Cada slot da lista e cada entry do mapa tem seu próprio ciclo de inicialização. O slot payments pode existir sem que refunds tenha sido criado.
Qual é a diferença entre LazyConstant e um campo final normal?
| Característica | final | LazyConstant |
|---|---|---|
| Momento da inicialização | No construtor (imediato) | Na primeira chamada a .get() |
| Necessidade de sincronização | Não (já é thread‑safe) | Internamente thread‑safe |
| Custo de criação | Pago sempre, mesmo que nunca usado | Pago somente se for usado |
| Flexibilidade | Valor deve estar pronto na construção | Pode depender de recursos que ainda não existem |
// final obriga inicialização imediata
private final ExpensiveClient client = new ExpensiveClient(); // roda no construtor
// LazyConstant inicializa na primeira chamada a .get()
private final LazyConstant client =
LazyConstant.of(ExpensiveClient::new);Depois de inicializado, a JVM pode tratar o LazyConstant como constante e aplicar as mesmas otimizações de um final estático.
O que preciso saber antes de usar Lazy Constants no Java 26?
- Ainda é preview – para compilar e rodar, habilite o preview:
javac --enable-preview --release 26 PaymentService.java
java --enable-preview PaymentServiceLazyConstantnão éSerializable. Caso precise serializar o objeto, ele não funcionará.- O valor armazenado é imutável, mas o objeto interno pode ser mutável. O
LazyConstantgarante que a referência é atribuída uma única vez; o que você faz com o objeto depois é responsabilidade sua.
Quando usar (e quando não usar) LazyConstant?
Use quando:
- O objeto é caro de criar e nem sempre necessário.
- Precisa ser thread‑safe sem gerenciar sincronização manualmente.
- Exemplos: conexões de banco, clientes HTTP, caches de configuração, registradores de métricas.
Não use quando:
- Um
finalsimples resolve o problema. - Precisa de serialização.
- Quer controle exato do momento da inicialização (por exemplo, antes de um ponto específico de inicialização da aplicação).
LazyConstant garante que a inicialização ocorre antes do primeiro .get(), mas não em um ponto arbitrário que você escolha.
Por que isso importa agora, sendo ainda preview?
O double‑checked locking está em produção em milhares de sistemas Java. Não porque alguém goste da complexidade, mas porque não havia alternativa melhor. O JEP 526 é a resposta do OpenJDK a esse problema.
A feature ainda pode mudar antes de ser finalizada, e a segunda preview serve exatamente para coletar feedback. Contudo, a direção está clara: a plataforma quer tratar a inicialização “lazy” como um primitivo de primeira classe, eliminando a necessidade de padrões boilerplate e propensos a erros.
LazyConstant – Abstração de Primeira Classe
Use a feature como abstração de primeira classe, com suporte do compilador e da JVM, em vez de uma “receita de código” que cada desenvolvedor implementa à sua maneira. Experimentar agora com --enable-preview é a forma mais direta de entender o impacto no seu código antes que a feature chegue como estável.
Perguntas frequentes sobre LazyConstant e JEP 526 no Java 26
LazyConstant é thread‑safe?
Sim. A inicialização única vale em ambientes multithread sem precisar de synchronized ou volatile.
Posso usar LazyConstant com Serializable?
Não. Se o objeto precisa de serialização, use final ou outro mecanismo.
LazyConstant substitui o padrão Singleton com classe interna estática?
Para a maioria dos casos, sim. O resultado é o mesmo, mas o código fica mais direto e a intenção fica explícita no tipo.
Preciso de Java 26 para usar essa feature?
Sim, com --enable-preview. O JEP 526 está em second preview e ainda não é estável.
O que acontece se a função de inicialização lançar uma exceção?
A exceção é propagada para quem chamou .get() e o LazyConstant não fica marcado como inicializado. A próxima chamada tenta novamente.
Sobre o autor
Eu sou o Luis De Llamas, Developer Advocate na act digital, Oracle ACE e IBM Champion.
Se quiser acompanhar mais conteúdo sobre Quarkus, Java e o que rola no ecossistema, me encontra aqui: