O lançamento do Java Development Kit (JDK) 23 (em setembro de 2024) constitui um marco significativo na história da linguagem de programação Java, que é uma ferramenta robusta, versátil e crucial no âmbito do desenvolvimento de software.

 

Desde 1995, quando o Java foi apresentado pela primeira vez pela Sun Microsystems com o slogan “Write One, Run Anywhere” (em português, algo como “Escreva uma vez, execute em qualquer lugar”), que a linguagem tem proporcionado a possibilidade de executar código em qualquer dispositivo. Ao longo dos anos, o Java tem evoluído através de várias versões, apresentando novas funcionalidades e melhorias que mantêm a compatibilidade com as versões anteriores.


A tradição mantém-se com esta nova versão, que tem como objetivo melhorar a produtividade dos developers e o desempenho das aplicações, assegurando ao mesmo tempo que o Java continua a ser relevante e poderoso.

 

 

O que está para vir no Java 23

Esta versão inclui um total de 12 Java Enhancement Proposals (JEPs), nomeadamente:


Novas funcionalidades
  • Primitive Types in Patterns, instanceof, and switch (funcionalidade em preview)
  • Markdown Documentation Comments
  • Module Import Declarations (funcionalidade em preview)

 

Funcionalidades melhoradas
  • Class-File API (funcionalidade em segundo preview)
  • Vector API (oitavo incubador)
  • Stream Gatherers (funcionalidade em segundo preview)
  • ZGC: Generational Mode by Default
  • Implicitly Declared Classes and Instance Main Methods (funcionalidade em terceiro preview)
  • Structured Concurrency (funcionalidade em terceiro preview)
  • Scoped Values (funcionalidade em terceiro preview)
  • Flexible Constructor Bodies (funcionalidade em segundo preview)

 

Funcionalidades obsoletas
  • Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal

 

Vejamos algumas das atualizações mais relevantes.

 

 

Algumas melhorias linguísticas

Primitive Types in Patterns, instanceof, and switch

Esta nova funcionalidade tem os seguintes objetivos:

  • Permitir a exploração uniforme de dados, possibilitando type patterns para todos os types, sejam eles primitivos ou de referência.
  • Alinhar type patterns com instanceof e alinhar instanceof com safe casting.
  • Permitir que o pattern matching utilize type patterns primitivos em contextos aninhados e de nível superior.
  • Fornecer constructs que eliminam o risco de perda de informações devido a casts inseguros.
  • Permitir que o switch processe valores de qualquer tipo primitivo. Esta melhoria torna a linguagem Java mais uniforme e expressiva.

 

Exemplo:

//improve the switch expression:

switch (x.getStatus()) {

    case 0 -> "ok";

    case 1 -> "warning";

    case 2 -> "error";

    default -> "unknown status: " + x.getStatus();

}

//exposing the matched value:

switch (x.getStatus()) {

    case 0 -> "okay";

    case 1 -> "warning";

    case 2 -> "error";

    case int i -> "unknown status: " + i;

}

//allowing guards to inspect the corresponding value:

switch (x.getYearlyFlights()) {

    case 0 -> ...;

    case 1 -> ...;

    case 2 -> issueDiscount();

    case int i when i >= 100 -> issueGoldCard();

    case int i -> ... appropriate action when i > 2 && i < 100 ...

}

 

Mais informação sobre esta funcionalidade aqui.

 

 

Flexible Constructor Bodies

Na linguagem de programação Java, os constructors permitem que as instruções apareçam antes de uma invocação explícita do constructor, como super(..) ou this(..). Embora essas instruções não possam referenciar a instância em construção, podem iniciar os seus campos.


A iniciação de campos antes de invocar outro constructor aumenta a fiabilidade da classe, particularmente quando os métodos são substituídos. Esta funcionalidade está atualmente em preview.

 

Exemplo:

//Flexible Constructor Bodies

class Parent {

    int x;

 

    public Parent(int x) {

        this.x = x;

    }

}

 

class Child extends Parent {

    int y;

 

    public Child(int x, int y) {

        // Statements before calling the parent constructor

        int temp = x * 2; // Cannot reference instance fields

        super(temp); // Explicit constructor invocation

        this.y = y; // Instance fields can be initialized after the invocation

    }

}

 

public class Main {

    public static void main(String[] args) {

        Child child = new Child(5, 10);

        System.out.println("x: " + child.x + ", y: " + child.y); // Outputs: x: 10, y: 10

    }

}

 

Mais informação sobre esta funcionalidade aqui.

 

 

Structured Concurrency

A funcionalidade de structured concurrency simplifica a programação multithread, tratando várias tarefas executadas em diferentes threads como uma única unidade de trabalho, simplificando assim o tratamento e o cancelamento de erros.

 

Exemplo:

 

// Structured Concurrency

public class StructuredConcurrencyExample {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

            Future<String> future1 = scope.fork(() -> fetchDataFromService1());

            Future<String> future2 = scope.fork(() -> fetchDataFromService2());

 

            scope.join(); // Join both forks

            scope.throwIfFailed(); // Propagate exceptions

 

            String result1 = future1.resultNow();

            String result2 = future2.resultNow();

            System.out.println(result1 + " " + result2);

        }

    }

 

    private static String fetchDataFromService1() {

        // Simulate fetching data

        return "Data1";

    }

 

    private static String fetchDataFromService2() {

        // Simulate fetching data

        return "Data2";

    }

}


Mais informação sobre esta funcionalidade aqui.

 

 

Scoped Values

Os scoped values permitem que os métodos partilhem dados imutáveis com as suas chamadas dentro de uma thread e com threads secundárias. São mais simples de compreender do que as variáveis de thread locais e oferecem menores custos de espaço e tempo.


Quando usados com virtual threads e structured concurrency, os scoped values são particularmente eficientes. Esta funcionalidade é atualmente uma API em preview.

 

Exemplo:

 

import java.util.concurrent.Executors;

import java.util.concurrent.ExecutorService;

import jdk.incubator.concurrent.ScopedValue;

 

public class Main {

    public static void main(String[] args) {

        ScopedValue<String> scopedValue = ScopedValue.create("Hello, World!");

 

        ExecutorService executor = Executors.newSingleThreadExecutor();

        executor.submit(() -> {

            System.out.println("Scoped Value: " + scopedValue.get());

        });

 

        executor.shutdown();

    }

}


Mais informação sobre esta funcionalidade aqui.

 

 

Stream Gatherers

Esta funcionalidade tem como objetivo melhorar a Java Stream API, introduzindo duas novas operações terminais: 'Stream.gather(toList(), toSet())' e 'Stream.gather(toMap(), toSet())'. Estas operações permitem recolher elementos de um stream em várias coleções simultaneamente, o que simplifica o código e melhora a legibilidade quando um stream precisa de ser recolhido em vários containers.


A funcionalidade, inicialmente proposta no Java 22, foi aperfeiçoada com base nos comentários e está agora a ser apresentada para uma segunda preview, de modo a garantir que a sua funcionalidade e desempenho satisfazem as necessidades da comunidade antes de ser finalizada.

 

Exemplo:

import java.util.List;

import java.util.Set;

import java.util.stream.Collectors;

import java.util.stream.Stream;

 

public class StreamGatherersExample {

    public static void main(String[] args) {

        // Example stream

        Stream<String> stream = Stream.of("apple", "banana", "cherry", "apple", "date", "banana");

 

        // Using Stream.gather to collect elements into a List and a Set simultaneously

        var result = stream.collect(Collectors.gather(

            Collectors.toList(),

            Collectors.toSet()

        ));

 

        // Extracting the results

        List<String> list = result.getFirst();

        Set<String> set = result.getSecond();

 

        // Printing the results

        System.out.println("List: " + list);

        System.out.println("Set: " + set);

    }

}

 

 

Mais informação sobre esta funcionalidade aqui.

 

 

Boas práticas para o desenvolvimento em Java 23

Eis as minhas dicas para os developers Java que utilizam o novo Java 23:

 

Adotar técnicas de codificação eficientes:

  1. Utilizar pattern matching para instruções switch: simplifica a lógica do código e reduz as estruturas aninhadas, permitindo um código mais conciso e legível.
  2. Otimizar registos: personalizar o comportamento de serialização dos registos para melhorar o encapsulamento dos dados e simplificar as práticas de programação orientadas para os objetos. Isto promove a eficiência e a facilidade de manutenção do código.
  3. Tirar partido da Vector API melhorada: aproveitar as melhorias na Vector API para cálculos numéricos, de forma a aumentar o desempenho, especialmente em tarefas que envolvem operações matriciais e computação científica.


Guidelines para o tratamento de erros:

  1. Adotar frameworks de serialização modernos: transição de API de serialização legacy para frameworks modernos como Protocol Buffers ou JSON, de modo a garantir compatibilidade futura e melhores capacidades de intercâmbio de dados.
  2. Implementar práticas de codificação seguras: adotar princípios e ferramentas de codificação seguros fornecidos no Java 23 para mitigar vulnerabilidades como ataques de injeção e exposição de dados, melhorando a segurança das aplicações.
  3. Afinar a Garbage Collection: otimizar os algoritmos de garbage collection para minimizar as pausas e otimizar a utilização da memória, contribuindo para um melhor desempenho das aplicações e para reduzir a sobrecarga de recursos.

 

Estratégias de otimização de desempenho:

  1. Utilizar melhorias na compilação Just-In-Time (JIT): beneficiar das otimizações do compilador JIT para compilar dinamicamente percursos de código frequentemente executados, resultando em velocidades de execução mais rápidas e melhores tempos de resposta.
  2. Otimizar o desempenho do HTTP Client: utilizar otimizações simplificadas do HTTP Client para uma melhor comunicação com os serviços web, incluindo um melhor agrupamento de ligações e tratamento dos pedidos/respostas.
  3. Reduzir o consumo de memória: implementar técnicas de gestão de memória para reduzir o consumo de memória das aplicações Java, garantindo uma alocação e utilização mais eficientes da memória.

 

 

Conclusão

O Java 23 introduz um conjunto de funcionalidades avançadas e melhorias concebidas para melhorar a eficiência e o desempenho do desenvolvimento de software, reforçando simultaneamente a compatibilidade e a interoperabilidade do Java com outras linguagens de programação e plataformas. Esta versão inclui melhorias para a Java Virtual Machine (JVM), novas funcionalidades de linguagem e atualizações para API e bibliotecas existentes, tudo com o objetivo de fornecer aos developers mais ferramentas para escreverem código de elevado desempenho e de fácil manutenção. As funcionalidades de destaque incluem melhorias no pattern matching para expressões switch, melhorias na foreign function e na API de memória, e novas opções de garbage collection, que contribuem coletivamente para a robustez e adaptabilidade das aplicações Java em diversos ambientes.


Como developer de software Java sénior, considero as novas funcionalidades do Java 23 empolgantes e essenciais para o desenvolvimento moderno. As melhorias no pattern matching simplificam a lógica condicional complexa, tornando o código mais legível e fácil de manter. As melhorias na foreign function e na API de memória são particularmente importantes, uma vez que abrem novas possibilidades de integração de Java com código nativo e outras linguagens, aumentando a versatilidade de Java em ecossistemas multilinguagem.


Em geral, o Java 23 continua a tradição de evoluir a linguagem e a plataforma para satisfazer as necessidades de desenvolvimento contemporâneas, garantindo que o Java continua a ser uma escolha de topo para os developers de todo o mundo.

Partilha este artigo