A Chegada do Java 22: Melhorias na Linguagem
Lançada no dia 19 de Março de 2024, a versão 22 do Java marca a primeira release não-LTS após a versão 21 e trouxe 12 melhorias, incluindo 7 preview features e 1 incubator feature. Essas features cobrem melhorias na linguagem, APIs, performance e nas ferramentas incluídas na JDK.
Neste artigo, falaremos especificamente sobre as melhorias feitas na linguagem. Mas antes de falar sobre elas, vamos reforçar alguns pontos sobre JEPs, preview features e as incubator features.
Java Enhancement Proposals (JEPs)
Java Enhancement Proposals (ou JEPs) são documentos oficiais que descrevem propostas de melhorias ou novas funcionalidades para a plataforma Java.
Elas desempenham um papel crucial na evolução da linguagem, servindo como um meio de comunicação detalhada e formal entre desenvolvedores da plataforma e a comunidade de usuários.
Cada JEP descreve uma alteração específica, como a introdução de uma nova API, melhorias de performance, mudanças na linguagem Java ou atualizações na JVM (Java Virtual Machine).
O objetivo delas é fornecer uma descrição clara e detalhada para cada mudança proposta, incluindo a justificativa, especificações técnicas, impactos previstos e planos de implementação.
Preview Features e Incubator Features
Nas releases do Java temos as Preview features e Incubator features, que são usadas para adicionar e coletar feedbacks sobre novos recursos e APIs antes de integrá-los totalmente à JDK. Mas qual a diferença entre elas?
Incubator Features
As incubator features (features em incubação) tem o objetivo de disponibilizar aos desenvolvedores APIs e ferramentas que estão em um estágio mais “cru” no processo de desenvolvimento.
Podendo permanecer nesse estado por um período indeterminado, essas features podem sofrer grandes mudanças até o estágio final e são tratadas como features experimentais.
Preview Features
As preview features (features em prévia) são features que estão totalmente implementadas e especificadas, mas que ainda não foram totalmente integradas a JDK pois precisam de feedback referente ao seu uso em cenários do mundo real.
Ponto de Atenção
Vale ressaltar que a quantidade de releases que uma feature pode continuar como incubator ou preview depende de diversos fatores, e ainda existe a chance de que ela possa ser removida em uma versão futura. Portanto, tome cuidado ao usá-las em produção.
Lista de JEPs Integradas ao Java 22
Apesar de falarmos apenas das melhorias na linguagem neste artigo, segue abaixo a lista de todas as JEPs (Java Enhancement Proposal) que foram integradas a versão 22 da JDK:
Melhorias na Linguagem
Bibliotecas
Performance
Ferramentas
Melhorias na Linguagem
A seguir, uma explicação mais elaborada e com exemplos retirados das JEPs sobre cada uma das melhorias feitas na linguagem, que contam com uma feature finalizada e três features em preview.
Observação
Para que você consiga executar os exemplos de preview features é necessário compilar e executar o arquivo utilizando a flag — enable-preview ou habilitar as preview features na sua IDE:
# Compilar com a flag --enable-preview
javac --release 22 --enable-preview Main.java
# Executar com a flag --enable-preview
java --enable-preview MainJEP-456: Unnamed Variables & Patterns
Com o objetivo de melhorar a legibilidade do código e reduzir a possibilidade de erros, a JEP-456 permite o uso do sublinhado (_) para declarar variáveis ou padrões não nomeados.
Esse novo recurso pode ser utilizado de diversas formas diferentes e afeta a definição de variáveis, inicialização de variáveis em loops, parâmetros de lambdas, blocos catch e try-with-resources, switch cases e também a validação de type patterns.
Variáveis Não Nomeadas
Sempre que uma variável não nomeada for declarada, é obrigatória a sua inicialização, seja ela uma variável local ou uma variável declarada em try-with-resources.
É importante notar que uma variável não nomeada não adiciona um nome no escopo, então após a sua inicialização, a variável não pode ser lida ou alterada.
Abaixo, alguns exemplos de como variáveis não inicializadas podem ser utilizadas:
// Atribuição de Valores de Variáveis
var _ = order.addProduct(product);
// o retorno de addProduct não é utilizado// Inicialização de Variáveis em Loops
for (int i = 0, _ = sideEffect(); i < 10; i++) {
// Código onde o retorno de ‘sideEffect()’ não é utilizado
}
for (Order _ : orders) {
// Código onde o valor de ‘order’ não é utilizado
}// Definição de Parâmetros de Lambdas
map.computeIfAbsent(key, _-> new ArrayList<>());
// Código onde o ‘index’ não é utilizado// Definição de Parâmetros de Blocos Catch
try {
// Código que pode lançar exceção
} catch (SomeException _) {
// Código onde a exceção não é utilizada
}// Atribuição de Recursos em Blocos Try-With-Resources
try (var _ = ScopedContext.acquire()) {
// Código onde o retorno de ‘acquire()’ não é utilziado
}Variáveis de Padrão Não Nomeadas
Variáveis também podem ser declaradas por type patterns (conhecidas como pattern variables), e esses type patterns também podem acabar declarando variáveis que não são utilizadas. Por exemplo:
switch (ball) {
case RedBall red -> process(ball);
case BlueBall blue -> process(ball);
case GreenBall green -> stopProcessing();
}
// Variáveis ‘red’, ‘blue’ e ‘green’ não são utilizadasAgora é possível definir type patterns não nomeados para definir padrões contendo variáveis que não serão utilizadas:
switch (ball) {
case RedBall _ -> process(ball);
case BlueBall _ -> process(ball);
case GreenBall _ -> stopProcessing();
}
// Utilizado o caractere ‘_’ para definir as variáveis não nomeadasAlém disso, a declaração de vários padrões em apenas um switch case é suportada. Vale notar que, de acordo com a JEP, um switch case com vários padrões declarados é executado se qualquer um dos padrões for verdadeiro:
switch (ball) {
case RedBall _, BlueBall _ -> process(ball);
case GreenBall _ -> stopProcessing();
}
// O primeiro case foi alterado para definir mais de um padrãoPadrões Não Nomeados na Desconstrução de Records
Podemos utilizar type patterns não nomeados na desconstrução de records para substituir componentes na verificação de padrões e extrair apenas as propriedades que serão utilizadas:
if (point instanceof ColoredPoint(Point(int x, int y), _)) {
// código que usa ‘x’ e ‘y’
}
// ou
if (point instanceof ColoredPoint(_, Color color)) {
// código que utiliza ‘color’
}Outro exemplo utilizando a desconstrução de records com type patterns não nomeados e múltiplas condições num switch case:
switch (box) {
case Box(RedBall _), Box(BlueBall _) -> processBox(box);
case Box(GreenBall _) -> stopProcessing();
case Box(var _) -> pickAnotherBox();
}Switch Case Guards
Outra adição feita nos switch case foi a possibilidade de declarar um guard para torná-lo ainda mais específico. Vale notar que apenas um guard pode ser definido em um case e que guards governam um case como um todo, ou seja, tanto a condição do case quanto o guard devem ser verdadeiros para que o case seja executado:
// considere a existência de uma variável ‘int x’
switch (ball) {
case RedBall _, BlueBall _ when x == 42 -> process(ball);
case GreenBall _ -> stopProcessing();
}
// O primeiro case tem um guard que verifica se x == 42O mesmo também se aplica para o uso de padrões não nomeados na desconstrução de records:
switch (box) {
case Box(RedBall _), Box(BlueBall _) when x == 42 -> processBox(box);
case Box(GreenBall _) -> stopProcessing();
case Box(var _) -> pickAnotherBox();
}* Exemplos retirados ou baseados nos exemplos e descrição da JEP-456
[Preview] JEP-447: Statements before super (…)
Trazendo mais liberdade para adicionar lógica nos construtores, a JEP-447 trás a primeira preview da feature que permite que instruções sejam escritas antes de chamadas a super(...) e this(...), facilitando a validação e a preparação de argumentos sem a necessidade de métodos auxiliares.
Apesar de algo simples, essa feature afeta não só a forma de trabalhar com herança, mas também a forma que podemos implementar a construção de Records e Enums.
Herança e Subclasses
Para contornar o comportamento padrão referente à chamadas de super(...) no início de construtores de subclasses, se tornou comum a implementação de métodos auxiliares, o uso de static factories com o auxilio de construtores privados ou até mesmo executar as validações após a chamada do super(...):
// Exemplo de método auxiliar para executar validações
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
super(verifyPositive(value)); // Necessidade de validar o valor do parâmetro value
}
// Método auxiliar para validar se o valor é positivo
private static String verifyPositive(long value) {
if (value <= 0) {
throw new IllegalArgumentException(”non-positive value”);
}
return String.valueOf(value);
}
}// Static Factory em Conjunto com Construtor Privado
public class PositiveBigInteger extends BigInteger {
private PositiveBigInteger(String value) { // Construtor privado
super(value);
}
// Construtor estático para executar validações
public static PositiveBigInteger createPositiveBigInteger(long value) {
if (value <= 0) {
throw new IllegalArgumentException(”non-positive value”);
}
return new PositiveBigInteger(String.valueOf(value));
}
}Instruções antes do super(…)
Com a entrada da JEP-447 os mesmos exemplos mostrados acima podem ser refatorados da seguinte forma:
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
if (value <= 0) { // Validação feita diretamente no construtor da subclasse
throw new IllegalArgumentException(”non-positive value”);
}
super(String.valueOf(value));
}
}Records e Enums
Apesar de não ter suporte à herança, records e enums também se beneficiam com essa nova feature, já que agora podemos utilizar instruções antes de chamar um construtor alternativo:
public record Email(String local, String domain) {
public Email(String email) {
Objects.requireNonNull(email);
var parts = email.split(”@”);
if (parts.length != 2) {
throw new IllegalArgumentException(”E-mail is not valid”);
}
this(parts[0], parts[1]);
}
}* Exemplos retirados ou baseados nos exemplos e descrição da JEP-447
[2nd Preview] JEP-459: String Templates
A JEP-459 trás a segunda preview das String Templates, feature que permite fazer interpolações em strings de uma forma mais legível e segura, tornando possível a combinação de textos e expressões para se obter resultados mais especializados.
O processamento da interpolação de strings pode ser feito pelos novos processadores de templates STR e FMT , através de um processador de template customizado que pode ser criado através da API de Processadores de Template.
O Processador de Templates STR
STR é um processador de template que realiza interpolação de string substituindo cada expressão incorporada no template pelo valor dessa expressão transformado em texto. Esse processador é capaz de executar operações, cálculos e acessar métodos e propriedades:
// Interpolação Simples
String name = “John”;
String text = STR.”Hello, \{name}”;
// Hello, John// Execução de Expressões Aritméticas
int x = 10;
int y = 20;
String text = STR.”\{x} + \{y} = \{x + y}”;
// “10 + 20 = 30”// Chamada de Métodos e Acesso a Propriedades
String offerTypeMessage = STR.”You have a \{getOfferType()} waiting for you!”;
// “You have a gift waiting for you!”
String accessMessage = STR.”Access at \{req.date} \{req.time} from \{req.ipAddress}”;
// “Access at 2022-03-25 15:34 from 8.8.8.8”// Expressões Complexas
String time = STR.”The time is \{
DateTimeFormatter
.ofPattern(”HH:mm:ss”)
.format(LocalTime.now())
} right now”;
// “The time is 12:34:56 right now”// Expressões de Múltiplas Linhas
String name = “Joan Smith”;
String phone = “555-123-4567”;
String address = “1 Maple Drive, Anytown”;
String json = STR.”“”
{
“name”: “\{name}”,
“phone”: “\{phone}”,
“address”: “\{address}”
}
“”“;
//”“”
//{
// “name”: “Joan Smith”,
// “phone”: “555-123-4567”,
// “address”: “1 Maple Drive, Anytown”
// }
// “”“O Processador de Templates FMT
Assim como o processador STR, o processador FMT também consegue executar interpolações de string e tem acesso as mesmas funcionalidades. Além disso, ele consegue executar formatações de acordo com o que foi definido em conjunto com as expressões:
// Formatação de valores interpolados
String table = FMT.”“”
Description Width Height Area
%-12s\{zone[0].name} %7.2f\{zone[0].width} %7.2f\{zone[0].height} %7.2f\{zone[0].area()}
%-12s\{zone[1].name} %7.2f\{zone[1].width} %7.2f\{zone[1].height} %7.2f\{zone[1].area()}
%-12s\{zone[2].name} %7.2f\{zone[2].width} %7.2f\{zone[2].height} %7.2f\{zone[2].area()}
\{” “.repeat(28)} Total %7.2f\{zone[0].area() + zone[1].area() + zone[2].area()}
“”“;
// “”“
// Description Width Height Area
// Alfa 17.80 31.40 558.92
// Bravo 9.60 12.40 119.04
// Charlie 7.10 11.23 79.73
// Total 757.69
// “”“API de Processadores de Template
Agora também é possível criar processadores de templates customizados com a utilização da API de Processadores de Template. Abaixo, um exemplo de como criar um processador de template que processa a interpolação de uma string e retorna um JSONObject:
// Processador de Template com retorno customizado
var JSON = StringTemplate.Processor.of(
(StringTemplate st) -> new JSONObject(st.interpolate())
);
String name = “Joan Smith”;
String phone = “555-123-4567”;
String address = “1 Maple Drive, Anytown”;
JSONObject jsonObject = JSON.”“”
{
“name”: “\{name}”,
“phone”: “\{phone}”,
“address”: “\{address}”
};
“”“;* Exemplos retirados ou baseados nos exemplos e descrição da JEP-459
[2nd Preview] JEP-463: Implicitly Declared Classes and Instance Main Methods
A JEP-463 traz a segunda preview da feature de Classes e Métodos Main Declarados Implicitamente, que tem o objetivo de evoluir a linguagem Java para que estudantes possam criar seus primeiros programas sem a necessidade de entender recursos mais complexos da linguagem.
Com essa feature, agora se torna possível implementar programas mais básicos de forma simples e introduzir conceitos e recursos mais avançados de forma gradual.
Método main() Simplificado
Agora, é possível implementar o método main() como um método de instância, sem a necessidade do modificador de visibilidade, de ser estático ou de receber argumentos:
class HelloWorld {
void main() {
System.out.println(”Hello, World!”);
}
}Classe Implicitamente Declarada
Para simplificar ainda mais, o compilador agora trata o método main() como parte de uma classe implicitamente declarada, permitindo declará-lo sem a necessidade de especificar uma classe de forma explícita:
void main() {
System.out.println(”Hello, World!”);
}Métodos e variáveis também podem ser criados dentro da classe implicitamente declarada:
String greetingMessage = “Hello, World!”;
String greetingMethod() {
return “Hello, World!”;
}
void main() {
System.out.println(greetingMessage);
System.out.println(greetingMethod());
}* Exemplos retirados ou baseados nos exemplos e descrição da JEP-463
Conclusão
O Java 22 traz várias melhorias que visam tornar a linguagem ainda mais eficiente, expressiva e acessível.
As variáveis e padrões não nomeados e os templates de string dão ao código uma maior legibilidade e expressividade. E a possibilidade de declarar métodos e atributos sem a necessidade de ter todo um entendimento sobre orientação a objetos torna a linguagem mais acessível para iniciantes.
Mas mesmo assim, é importante lembrar que muitas dessas features ainda estão em preview e podem sofrer alterações em versões futuras. Então tome cuidado ao utilizá-las em seus projetos.



